Initial commit.
This commit is contained in:
commit
f0c91b4e55
78 changed files with 5054 additions and 0 deletions
127
src/schemas/channelSchema.js
Normal file
127
src/schemas/channelSchema.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/>.*/
|
||||
|
||||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
//Local Imports
|
||||
const statSchema = require('./statSchema.js');
|
||||
|
||||
const channelSchema = new mongoose.Schema({
|
||||
id: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
description: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
thumbnail: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
default: "img/johnny.png"
|
||||
},
|
||||
settings: {
|
||||
hidden: {
|
||||
type: mongoose.SchemaTypes.Boolean,
|
||||
required: true,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
channelSchema.pre('save', async function (next){
|
||||
if(this.isModified("name")){
|
||||
if(this.name.match(/^[a-z0-9_\-.]+$/i) == null){
|
||||
throw new Error("Username must only contain alpha-numerics and the following symbols: '-_.'");
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
//statics
|
||||
channelSchema.statics.register = async function(channelObj){
|
||||
const {name, description, thumbnail} = channelObj;
|
||||
|
||||
const chanDB = await this.findOne({ name });
|
||||
|
||||
if(chanDB){
|
||||
throw new Error("Channel name already taken!");
|
||||
}else{
|
||||
const id = await statSchema.incrementChannelCount();
|
||||
const newChannel = await this.create((thumbnail ? {id, name, description, thumbnail} : {id, name, description}));
|
||||
}
|
||||
}
|
||||
|
||||
channelSchema.statics.getChannelList = async function(includeHidden = false){
|
||||
const chanDB = await this.find({});
|
||||
var chanGuide = [];
|
||||
|
||||
//crawl through channels
|
||||
chanDB.forEach((channel) => {
|
||||
//For each channel, push an object with only the information we need to the channel guide
|
||||
if(!channel.settings.hidden || includeHidden){
|
||||
chanGuide.push({
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
description: channel.description,
|
||||
thumbnail: channel.thumbnail
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//return the channel guide
|
||||
return chanGuide;
|
||||
}
|
||||
|
||||
//methods
|
||||
channelSchema.methods.updateSettings = async function(settingsMap){
|
||||
settingsMap.forEach((value, key) => {
|
||||
if(this.settings[key] == null){
|
||||
throw new Error("Invalid channel setting.");
|
||||
}
|
||||
|
||||
this.settings[key] = value;
|
||||
})
|
||||
|
||||
await this.save();
|
||||
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
channelSchema.methods.nuke = async function(confirm){
|
||||
if(confirm == "" || confirm == null){
|
||||
throw new Error("Empty Confirmation String!");
|
||||
}else if(confirm != this.name){
|
||||
throw new Error("Bad Confirmation String!");
|
||||
}
|
||||
|
||||
//Annoyingly there isnt a good way to do this from 'this'
|
||||
var oldUser = await module.exports.deleteOne(this);
|
||||
|
||||
if(oldUser == null){
|
||||
throw new Error("Server Error: Unable to delete channel! Please report this error to your server administrator, and with timestamp.");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("channel", channelSchema);
|
||||
104
src/schemas/statSchema.js
Normal file
104
src/schemas/statSchema.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*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 config = require('./../../config.json');
|
||||
|
||||
const statSchema = new mongoose.Schema({
|
||||
//This does NOT handle deleted accounts/channels. Use userModel.estimatedDocumentCount() for number of active users.
|
||||
userCount: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
channelCount: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
launchCount: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
|
||||
//statics
|
||||
statSchema.statics.getStats = async function(){
|
||||
//Get the first document we find
|
||||
var stats = await this.findOne({});
|
||||
|
||||
if(stats){
|
||||
//If we found something then the statistics document exist and this is it,
|
||||
//So long as no one else has fucked with the database it should be the only one. (is this forshadowing for a future bug?)
|
||||
return stats;
|
||||
}else{
|
||||
//Otherwise this is the first launch of the install, say hello
|
||||
console.log("First launch detected! Initializing statistics document in Database!");
|
||||
|
||||
//create and save the statistics document
|
||||
stats = await this.create({});
|
||||
await stats.save();
|
||||
|
||||
//live up to the name of the function
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
statSchema.statics.incrementLaunchCount = async function(){
|
||||
//get our statistics document
|
||||
const stats = await this.getStats();
|
||||
|
||||
//increment counter and save
|
||||
stats.launchCount++;
|
||||
stats.save();
|
||||
|
||||
//print bootup message to console.
|
||||
console.log(`${config.instanceName}(Powered by Canopy) initialized. This server has booted ${stats.launchCount} time${stats.launchCount == 1 ? '' : 's'}.`)
|
||||
}
|
||||
|
||||
statSchema.statics.incrementUserCount = async function(){
|
||||
//get our statistics document
|
||||
const stats = await this.getStats();
|
||||
//temporarily keep old count so we can return it for the users ID
|
||||
const oldCount = stats.userCount;
|
||||
|
||||
//increment counter and save
|
||||
stats.userCount++;
|
||||
stats.save();
|
||||
|
||||
//return the count from beggining of function for user ID
|
||||
return oldCount;
|
||||
}
|
||||
|
||||
statSchema.statics.incrementChannelCount = async function(){
|
||||
//get our statistics document
|
||||
const stats = await this.getStats();
|
||||
//temporarily keep old count so we can return it for the channel ID
|
||||
const oldCount = stats.channelCount;
|
||||
|
||||
//increment counter and save
|
||||
stats.channelCount++;
|
||||
stats.save();
|
||||
|
||||
//return the count from beggining of function for channel ID
|
||||
return oldCount;
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("statistics", statSchema);
|
||||
210
src/schemas/userSchema.js
Normal file
210
src/schemas/userSchema.js
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/*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 server = require('../server.js');
|
||||
const statSchema = require('./statSchema.js');
|
||||
const hashUtil = require('../utils/hashUtils');
|
||||
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
id: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
},
|
||||
pass: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
email: {
|
||||
type: mongoose.SchemaTypes.String
|
||||
},
|
||||
date: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
},
|
||||
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,
|
||||
default: "Bio not set!"
|
||||
},
|
||||
signature: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
default: "Signature not set!"
|
||||
}
|
||||
});
|
||||
|
||||
//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(this.isModified("pass")){
|
||||
this.pass = hashUtil.hashPassword(this.pass);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
//statics
|
||||
userSchema.statics.register = async function(userObj){
|
||||
const {user, pass, passConfirm, email} = userObj;
|
||||
|
||||
if(pass == passConfirm){
|
||||
const userDB = await this.findOne({$or: email ? [{user}, {email}] : [{user}]});
|
||||
|
||||
if(userDB){
|
||||
throw new Error("User name/email already taken!");
|
||||
}else{
|
||||
const id = await statSchema.incrementUserCount();
|
||||
const newUser = await this.create({id, user, pass, email});
|
||||
}
|
||||
}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.");
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//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(){
|
||||
//get authenticated sessions
|
||||
var sessions = await this.getAuthenticatedSessions();
|
||||
|
||||
//crawl through and kill all sessions
|
||||
sessions.forEach((session) => {
|
||||
server.store.destroy(session.seshid);
|
||||
});
|
||||
}
|
||||
|
||||
userSchema.methods.passwordReset = 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();
|
||||
}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){
|
||||
if(pass == "" || pass == null){
|
||||
throw new Error("No confirmation password!");
|
||||
}
|
||||
|
||||
if(this.checkPass(pass)){
|
||||
//Annoyingly there isnt a good way to do this from 'this'
|
||||
var oldUser = await module.exports.deleteOne(this);
|
||||
|
||||
if(oldUser){
|
||||
await this.killAllSessions();
|
||||
}else{
|
||||
throw new Error("Server Error: Unable to delete account! Please report this error to your server administrator, and with timestamp.");
|
||||
}
|
||||
}else{
|
||||
throw new Error("Bad pass.");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("user", userSchema);
|
||||
Loading…
Add table
Add a link
Reference in a new issue