Initial commit.

This commit is contained in:
rainbownapkin 2024-11-15 17:44:03 -05:00
commit f0c91b4e55
78 changed files with 5054 additions and 0 deletions

View file

@ -0,0 +1,22 @@
/*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/>.*/
module.exports = class{
constructor(name){
this.name = name;
this.userList = new Map();
}
}

View file

@ -0,0 +1,112 @@
/*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/>.*/
//Local Imports
const channelModel = require('../../schemas/channelSchema');
const activeChannel = require('./activeChannel');
const chatHandler = require('./chatHandler');
//local variables
var activeChannels = new Map;
module.exports.handleConnection = async function(io, socket){
//Prevent logged out connections and authenticate socket
if(socket.request.session.user != null){
try{
//Set socket user and channel values
socket.user = socket.request.session.user;
socket.chanName = socket.handshake.headers.referer.split('/c/')[1];
//Check if channel exists
if(await channelModel.findOne({name: socket.chanName}) == null){
socket.disconnect("Channel does not exist!");
return;
}
//Check if current channel is active
var activeChan = activeChannels.get(socket.chan);
if(!activeChan){
//If not, make it so
activeChan = new activeChannel(socket.chan);
activeChannels.set(socket.chan, activeChan);
}
//Check if this user is already connected
if(activeChan.userList.get(socket.user.user)){
//get current list of socket connnections of selected user
var socketList = activeChan.userList.get(socket.user.user);
//Add this one on
socketList.push(socket.id);
//Set the user back socket list back
activeChan.userList.set(socket.user.user, socketList);
}else{
//if this is the first connection, initialize the socket array for this user
activeChan.userList.set(socket.user.user, [socket.id]);
}
//if everything looks good, admit the connection to the channel
socket.join(socket.chan);
//Send out the userlist
module.exports.broadcastUserList(io, socket.chan);
}catch(err){
socket.disconnect("Server Error During Channel Connection Initialization");
console.log(err);
return;
}
}else{
socket.disconnect("Unauthenticated");
return;
}
//Socket Listeners
socket.conn.on("close", (reason) => {
//Get active channel
var activeChan = activeChannels.get(socket.chan);
//If we have more than one active connection
if(activeChan.userList.get(socket.user.user).length > 1){
//temporarily store list of active user sockets
var socketList = activeChan.userList.get(socket.user.user);
//Filter out disconnecting socket from socket list, and set as current socket list for user
activeChan.userList.set(socket.user.user,socketList.filter((id) => {
return id != socket.id;
}))
}else{
activeChan.userList.delete(socket.user.user);
}
//and send out the filtered list
module.exports.broadcastUserList(io, socket.chan);
});
//define chat listeners
chatHandler.defineListeners(io, socket);
}
module.exports.broadcastUserList = function(io, chan){
var activeChan = activeChannels.get(chan);
var userList = [];
activeChan.userList.forEach((socketlist, user) => {
userList.push(user);
});
io.in(chan).emit("user-list", userList);
}

View file

@ -0,0 +1,39 @@
/*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 validator = require('validator');//No express here, so regular validator it is!
module.exports.defineListeners = function(io, socket){
socket.on("chat-message", (data) => {
//Trim and Sanatize for XSS
const msg = validator.trim(validator.escape(data.msg));
//make sure high is an int
const high = validator.toInt(data.high);
//nuke the message if its empty or huge
if(!validator.isLength(msg, {min: 1, max: 255})){
return;
}
//nuke the message if the high number is wrong
if(high < 0 || high > 10){
return;
}
io.in(socket.chan).emit("chat-message", {user: socket.user.user, msg, high});
});
}

View file

@ -0,0 +1,30 @@
/*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/>.*/
//Config
const config = require('../../config.json');
const channelModel = require('../schemas/channelSchema');
//register page functions
module.exports.get = async function(req, res){
try{
const chanGuide = await channelModel.getChannelList(true);
return res.render('adminPanel', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide});
}catch(err){
res.status(500);
return res.send("Error indexing channels!");
}
}

View file

@ -0,0 +1,60 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const userModel = require('../../../schemas/userSchema');
const accountUtils = require('../../../utils/sessionUtils.js');
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
//api account functions
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
//syntactic sugar :P
const user = req.session.user;
const data = matchedData(req);
//make sure we're not bullshitting ourselves here.
if(user == null){
res.status(400);
return res.send('Invalid Session! Cannot delete account while logged out!');
}
const userDB = await userModel.findOne(user);
if(!userDB){
res.status(400);
return res.send('Invalid User! Account must exist in order to delete!');
}
await userDB.nuke(data.pass);
accountUtils.killSession(req.session);
return res.sendStatus(200);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,45 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const accountUtils = require('../../../utils/sessionUtils.js');
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
//api account functions
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
const data = matchedData(req);
const {user, pass} = data;
//try to authenticate the session, and return a successful code if it works
await accountUtils.authenticateSession(user, pass, req);
return res.sendStatus(200);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,33 @@
/*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/>.*/
//local imports
const accountUtils = require('../../../utils/sessionUtils.js');
module.exports.get = async function(req, res){
if(req.session.user){
try{
accountUtils.killSession(req.session);
return res.sendStatus(200);
}catch(err){
res.status(400);
return res.send(err.message)
}
}else{
res.status(400);
return res.send()
}
}

View file

@ -0,0 +1,39 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const userModel = require('../../../schemas/userSchema');
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
const user = matchedData(req);
await userModel.register(user)
return res.sendStatus(200);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,89 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const userModel = require('../../../schemas/userSchema');
const accountUtils = require('../../../utils/sessionUtils.js');
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
module.exports.post = async function(req, res){
const validResult = validationResult(req);
const data = matchedData(req);
var tempResult = [];
//if we're not chaning the password
if(data.passChange == null){
//go through validation errors
validResult.errors.forEach(function(error, i){
//remove irrelevant password validation errors (i know its gross but they need to be optional as a set, not individually)
if(!error.path.startsWith("passChange.")){
tempResult.push(error);
}
});
validResult.errors = tempResult;
}
try{
if(validResult.isEmpty()){
const {field, change} = data;
const {user} = req.session;
const userDB = await userModel.findOne(user);
const update = {};
if(userDB){
if(data.img){
userDB.img = data.img;
update.img = data.img;
}
if(data.bio){
userDB.bio = data.bio;
update.bio = data.bio;
}
if(data.signature){
userDB.signature = data.signature;
update.signature = data.signature;
}
if(data.passChange){
//kill active session to prevent connect-mongo from freaking out
accountUtils.killSession(req.session);
await userDB.passwordReset(data.passChange);
}
await userDB.save();
res.status(200);
return res.send(update);
}else{
res.status(400);
return res.send({errors: [{msg:"User not found!"}]});
}
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,48 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
const channelModel = require('../../../schemas/channelSchema');
//api account functions
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
const data = matchedData(req);
const channel = await channelModel.findOne({name: data.chanName});
if(channel == null){
throw new Error("Chanenl does not exist!");
}
await channel.nuke(data.confirm);
return res.sendStatus(200);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,32 @@
/*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/>.*/
//local imports
const channelModel = require('../../../schemas/channelSchema');
//api account functions
module.exports.get = async function(req, res){
try{
//I'm not sanatizing this, it never gets processed...
const chanGuide = await channelModel.getChannelList(req.query.showHidden || req.query.showHidden === '');
res.status(200);
return res.send(chanGuide);
}catch(err){
res.status(400);
return res.send(err.message);
}
}

View file

@ -0,0 +1,42 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
const channelModel = require('../../../schemas/channelSchema');
//api account functions
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
const channel = matchedData(req);
if(validResult.isEmpty()){
await channelModel.register(channel)
return res.sendStatus(200);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,72 @@
/*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 {validationResult, matchedData} = require('express-validator');
//local imports
const {exceptionHandler} = require('../../../utils/loggerUtils.js');
const channelModel = require('../../../schemas/channelSchema');
//api account functions
module.exports.get = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
const data = matchedData(req);
const channel = await channelModel.findOne({name: data.chanName});
if(channel == null){
throw new Error("Channel not found.");
}
res.status(200);
return res.send(channel.settings);
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}
module.exports.post = async function(req, res){
try{
const validResult = validationResult(req);
if(validResult.isEmpty()){
const data = matchedData(req);
const channel = await channelModel.findOne({name: data.chanName});
const settingsMap = new Map(Object.entries(data.settingsMap));
if(channel == null){
throw new Error("Channel not found.");
}
res.status(200);
return res.send(await channel.updateSettings(settingsMap));
}else{
res.status(400);
res.send({errors: validResult.array()})
}
}catch(err){
exceptionHandler(res, err);
}
}

View file

@ -0,0 +1,23 @@
/*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/>.*/
//Config
const config = require('../../config.json');
//channel functions
module.exports.get = function(req, res){
res.render('channel', {instance: config.instanceName, user: req.session.user});
}

View file

@ -0,0 +1,36 @@
/*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/>.*/
//Config
const config = require('../../config.json');
const channelModel = require('../schemas/channelSchema');
//root index functions
module.exports.get = async function(req, res){
try{
const chanName = (req.url.slice(1).replace('/settings',''));
const channel = await channelModel.findOne({name: chanName});
if(channel == null){
throw new Error("Channel not found.");
}
return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel});
}catch(err){
res.status(500);
res.send(err.message);
}
}

View file

@ -0,0 +1,30 @@
/*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/>.*/
//Config
const config = require('../../config.json');
const channelModel = require('../schemas/channelSchema');
//root index functions
module.exports.get = async function(req, res){
try{
const chanGuide = await channelModel.getChannelList();
return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide});
}catch(err){
res.status(500);
return res.send("Error indexing channels!");
}
}

View file

@ -0,0 +1,23 @@
/*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/>.*/
//Config
const config = require('../../config.json');
//root index functions
module.exports.get = async function(req, res){
res.render('newChannel', {instance: config.instanceName, user: req.session.user});
}

View file

@ -0,0 +1,50 @@
/*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/>.*/
//Local Imports
const userModel = require('../schemas/userSchema');
//Config
const config = require('../../config.json');
//profile functions
module.exports.get = async function(req, res){
var profileName = req.url.slice(1) == '' ? (req.session.user ? req.session.user.user : null) : req.url.slice(1);
const userDB = await userModel.findOne({ user: profileName });
if(userDB){
res.render('profile', {instance: config.instanceName,
user: req.session.user,
profile: {
id: userDB.id,
user: userDB.user,
date: userDB.date,
tokes: userDB.tokes,
img: userDB.img,
signature: userDB.signature,
bio: userDB.bio
}
});
}else{
res.render('profile', {instance: config.instanceName,
user: req.session.user,
profile: null
});
}
}

View file

@ -0,0 +1,23 @@
/*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/>.*/
//Config
const config = require('../../config.json');
//register page functions
module.exports.get = function(req, res){
res.render('register', {instance: config.instanceName, user: req.session.user});
}

View file

@ -0,0 +1,30 @@
/*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 { Router } = require('express');
//local imports
const adminPanelController = require("../controllers/adminPanelController");
//globals
const router = Router();
//routing functions
router.get('/', adminPanelController.get);
module.exports = router;

View file

@ -0,0 +1,50 @@
/*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 { Router } = require('express');
//local imports
const {accountValidator} = require("../../utils/validators");
const loginController = require("../../controllers/api/account/loginController");
const logoutController = require("../../controllers/api/account/logoutController");
const registerController = require("../../controllers/api/account/registerController");
const updateController = require("../../controllers/api/account/updateController");
const deleteController = require("../../controllers/api/account/deleteController");
//globals
const router = Router();
//routing functions
router.post('/login', accountValidator.user(), accountValidator.pass(), loginController.post);
router.get('/logout', logoutController.get);
router.post('/register', accountValidator.user(),
accountValidator.pass(),
accountValidator.pass('passConfirm'),
accountValidator.email(), registerController.post);
router.post('/update', accountValidator.img(),
accountValidator.bio(),
accountValidator.signature(),
accountValidator.pass('passChange.oldPass'),
accountValidator.securePass('passChange.newPass'),
accountValidator.pass('passChange.confirmPass'), updateController.post);
router.post('/delete', accountValidator.pass(), deleteController.post);
module.exports = router;

View file

@ -0,0 +1,37 @@
/*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 { Router } = require('express');
//local imports
const {channelValidator} = require("../../utils/validators");
const registerController = require("../../controllers/api/channel/registerController");
const listController = require("../../controllers/api/channel/listController");
const settingsController = require("../../controllers/api/channel/settingsController");
const deleteController = require("../../controllers/api/channel/deleteController");
//globals
const router = Router();
//routing functions
router.post('/register', channelValidator.name(), channelValidator.description(), channelValidator.thumbnail(), registerController.post);
router.get('/list', listController.get);
router.get('/settings', channelValidator.name('chanName'), settingsController.get);
router.post('/settings', channelValidator.name('chanName'), channelValidator.settingsMap(), settingsController.post);
router.post('/delete', channelValidator.name('chanName'), channelValidator.name('confirm'),deleteController.post);
module.exports = router;

31
src/routers/apiRouter.js Normal file
View file

@ -0,0 +1,31 @@
/*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 { Router } = require('express');
//local imports
const accountRouter = require("./api/accountRouter");
const channelRouter = require("./api/channelRouter");
//globals
const router = Router();
//routing functions
router.use('/account', accountRouter);
router.use('/channel', channelRouter);
module.exports = router;

View file

@ -0,0 +1,32 @@
/*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 { Router } = require('express');
//local imports
const channelController = require("../controllers/channelController");
const channelSettingsController = require("../controllers/channelSettingsController");
//globals
const router = Router();
//routing functions
router.get('/*/settings', channelSettingsController.get);
router.get('/*/', channelController.get);
module.exports = router;

View file

@ -0,0 +1,30 @@
/*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 { Router } = require('express');
//local imports
const indexController = require("../controllers/indexController");
//globals
const router = Router();
//routing functions
router.get('/', indexController.get);
module.exports = router;

View file

@ -0,0 +1,30 @@
/*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 { Router } = require('express');
//local imports
const newChannelController = require("../controllers/newChannelController");
//globals
const router = Router();
//routing functions
router.get('/', newChannelController.get);
module.exports = router;

View file

@ -0,0 +1,30 @@
/*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 { Router } = require('express');
//local imports
const profileController = require("../controllers/profileController");
//globals
const router = Router();
//routing functions
router.get('/*', profileController.get);
module.exports = router;

View file

@ -0,0 +1,30 @@
/*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 { Router } = require('express');
//local imports
const registerController = require("../controllers/registerController");
//globals
const router = Router();
//routing functions
router.get('/', registerController.get);
module.exports = router;

View 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
View 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
View 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);

111
src/server.js Normal file
View file

@ -0,0 +1,111 @@
/*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/>.*/
//Define NPM imports
const express = require('express');
const session = require('express-session');
const {createServer } = require('http');
const { Server } = require('socket.io');
const path = require('path');
const mongoStore = require('connect-mongo');
const mongoose = require('mongoose');
//Define Local Imports
const statModel = require('./schemas/statSchema');
const channelManager = require('./app/channel/channelManager');
const indexRouter = require('./routers/indexRouter');
const registerRouter = require('./routers/registerRouter');
const profileRouter = require('./routers/profileRouter');
const adminPanelRouter = require('./routers/adminPanelRouter');
const channelRouter = require('./routers/channelRouter');
const newChannelRouter = require('./routers/newChannelRouter');
const apiRouter = require('./routers/apiRouter');
//Define Config
const config = require('../config.json');
const port = config.port;
const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`;
//Define Node JS
const app = express();
//Define session-store (exported so we can kill sessions from user schema)
module.exports.store = mongoStore.create({mongoUrl: dbUrl});
//define sessionMiddleware
const sessionMiddleware = session({
secret: config.sessionSecret,
resave: false,
saveUninitialized: false,
store: module.exports.store
});
//Define http and socket.io servers
const httpServer = createServer(app);
const io = new Server(httpServer, {});
//Connect mongoose to the database
mongoose.set("sanitizeFilter", true).connect(dbUrl).then(() => {
console.log("Connected to DB");
}).catch((err) => {
console.error("Unable to connecto to DB: ");
console.error(err);
process.exit();
});
//Set View Engine
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
//Middlware
//Enable Express
app.use(express.json());
//Enable Express-Sessions
app.use(sessionMiddleware);
//Enable Express-Session w/ Socket.IO
io.engine.use(sessionMiddleware);
//Routes
//Humie-Friendly
app.use('/', indexRouter);
app.use('/register', registerRouter);
app.use('/profile', profileRouter);
app.use('/adminPanel', adminPanelRouter);
app.use('/c', channelRouter);
app.use('/newChannel', newChannelRouter);
//Bot-Ready
app.use('/api', apiRouter);
//3rd-Party Browser-Side Libraries
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons')));
app.use('/lib/socket.io',express.static(path.join(__dirname, '../node_modules/socket.io/client-dist')));
app.use('/lib/validator',express.static(path.join(__dirname, '../node_modules/validator')));
//Static File Server
app.use(express.static(path.join(__dirname, '../www')));
//Increment launch counter
statModel.incrementLaunchCount();
//Hand over general-namespace socket.io connections to the channel manager
io.on("connection", (socket) => {channelManager.handleConnection(io, socket)} );
//Listen Function
httpServer.listen(port, () => {
console.log(`Opening port ${port}`);
});

26
src/utils/hashUtils.js Normal file
View file

@ -0,0 +1,26 @@
/*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/>.*/
const bcrypt = require('bcrypt');
module.exports.hashPassword = function(pass){
const salt = bcrypt.genSaltSync();
return bcrypt.hashSync(pass, salt);
}
module.exports.comparePassword = function(pass, hash){
return bcrypt.compareSync(pass, hash);
}

22
src/utils/loggerUtils.js Normal file
View file

@ -0,0 +1,22 @@
/*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/>.*/
//At some point this will be a bit more advanced, right now it's just a placeholder :P
module.exports.exceptionHandler = function(res, err){
//if not yell at the browser for fucking up, and tell it what it did wrong.
res.status(400);
return res.send({errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]});
}

48
src/utils/sessionUtils.js Normal file
View file

@ -0,0 +1,48 @@
/*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/>.*/
//local imports
const userModel = require('../schemas/userSchema.js');
//this module is good for keeping wrappers for userModel and other shit in that does more session handling than database access/modification.
module.exports.authenticateSession = async function(user, pass, req){
//Authenticate the session
userDB = await userModel.authenticate(user, pass);
//Tattoo the session with user and metadata
//unfortunately store.all() does not return sessions w/ their ID so we had to improvise...
//Not sure if this is just how connect-mongo is implemented or if it's an express issue, but connect-mongodb-session seems to not implement the all() function what so ever...
req.session.seshid = req.session.id;
req.session.authdate = new Date();
req.session.authip = req.ip;
req.session.user = {
user: userDB.user,
id: userDB.id
}
//userDB.activeSessions.push(sessionData);
await userDB.save();
//return user
return userDB.user;
}
module.exports.killSession = async function(session){
session.destroy();
}

50
src/utils/validators.js Normal file
View file

@ -0,0 +1,50 @@
/*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 { check, body, checkSchema, checkExact} = require('express-validator');
module.exports.accountValidator = {
user: (field = 'user') => body(field).escape().trim().isLength({min: 1, max: 22}),
//Password security requirements may change over time, therefore we should only validate against strongPassword() when creating new accounts
//that way we don't break old ones upon change
pass: (field = 'pass') => body(field).notEmpty().escape().trim(),
securePass: (field) => this.accountValidator.pass(field).isStrongPassword({minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1}),
email: (field = 'email') => body(field).optional().isEmail().normalizeEmail(),
img: (field = 'img') => body(field).optional().isURL({require_tld: false}).trim(),
signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 150}),
bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}),
}
module.exports.channelValidator = {
name: (field = 'name') => check(field).escape().trim().isLength({min: 1, max: 50}),
description: (field = 'description') => body(field).escape().trim().isLength({min: 1, max: 1000}),
thumbnail: (field = 'thumbnail') => this.accountValidator.img(field),
settingsMap: () => checkExact(checkSchema({
'settingsMap.hidden': {
optional: true,
isBoolean: true,
}
}))
}

63
src/views/adminPanel.ejs Normal file
View file

@ -0,0 +1,63 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="/css/adminPanel.css">
<title><%= instance %> - Admin Panel</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<div id="admin-channel-list-div" class="admin-channel-list">
<h3>Channel List:</h3>
<table id="admin-channel-list-table" class="admin-channel-list">
<tr id="admin-channel-list-entry-title" class="admin-channel-list-entry">
<td id="admin-channel-list-entry-img-title" class="admin-channel-list-entry admin-channel-list-entry-title admin-channel-list-entry-item admin-channel-list-entry-img-row">
<h3>Img</h3>
</td>
<td id="admin-channel-list-entry-name-title" class="admin-channel-list-entry admin-channel-list-entry-title admin-channel-list-entry-item admin-channel-list-entry-name-row">
<h3>Name</h3>
</td>
<td id="admin-channel-list-entry-description-title" class="admin-channel-list-entry admin-channel-list-entry-title admin-channel-list-entry-item">
<h3>Description</h3>
</td>
</tr>
<% chanGuide.forEach((channel) => { %>
<tr id="admin-channel-list-entry-<%- channel.name %>" class="admin-channel-list-entry">
<td id="admin-channel-list-entry-img-<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item admin-channel-list-entry-img-row">
<a href="/c/<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item">
<img id="admin-channel-list-entry-img-<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item" src="<%- channel.thumbnail %>">
</a>
</td>
<td id="admin-channel-list-entry-name-<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item admin-channel-list-entry-name-row">
<a href="/c/<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item">
<%- channel.name %>
</a>
</td>
<td id="admin-channel-list-entry-description-<%- channel.name %>" class="admin-channel-list-entry admin-channel-list-entry-item">
<p><%- channel.description %></p>
</td>
</tr>
<% }); %>
</table>
</div>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="js/adminPanel.js"></script>
</footer>
</html>

101
src/views/channel.ejs Normal file
View file

@ -0,0 +1,101 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="/css/channel.css">
<title><%= instance %> - *DISCONNECTED*</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<div class="channel" id="channel-flexbox">
<div class="media-panel" id="media-panel-div">
<div class="media-panel panel-head-div" id="media-panel-head-div">
<i class="media-panel panel-head-element bi-caret-down-fill" id="media-panel-div-toggle-icon"></i>
<p class="media-panel panel-head-element" id="media-panel-title-paragraph">Currently Playing: NULL</p>
<span class="media-panel panel-head-spacer-span" id="media-panel-head-spacer-span"></span>
<i class="media-panel panel-head-element bi-arrow-repeat" id="media-panel-sync-icon"></i>
<i class="media-panel panel-head-element bi-aspect-ratio-fill" id="media-panel-aspect-lock-icon"></i>
<i class="media-panel panel-head-element bi-film" id="media-panel-cinema-mode-icon"></i>
<i class="media-panel panel-head-element bi-arrows-vertical" id="media-panel-flip-vertical-icon"></i>
<i class="media-panel panel-head-element bi-arrows" id="media-panel-flip-horizontal-icon"></i>
<i class="media-panel panel-head-element bi-arrow-clockwise" id="media-panel-reload-icon"></i>
<i class="media-panel panel-head-element bi-chat-right-dots-fill" id="media-panel-show-chat-icon"></i>
</div>
<video src="/video/static.webm" class="media-panel" id="media-panel-video" muted loop autoplay></video>
</div>
<div class="chat-panel" id="chat-panel-div">
<div class="drag-handle" id="chat-panel-drag-handle">
</div>
<div class="chat-panel panel-head-div" id="chat-panel-head-div">
<i class="chat-panel panel-head-element bi-film" id="chat-panel-show-video-icon"></i>
<i class="chat-panel panel-head-element bi-caret-down-fill" id="chat-panel-div-hide"></i>
<select class="chat-panel panel-head-element" id="chat-panel-high-level-select">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select>
<p class="chat-panel panel-head-element" id="chat-panel-high-level-paragraph">/10</p>
<select class="chat-panel panel-head-element" id="chat-panel-flair-select">
<option>Flair</option>
</select>
<span class="chat-panel panel-head-spacer-span" id="chat-panel-head-spacer-span"></span>
<p class="chat-panel panel-head-element" id="chat-panel-user-count">NULL Users</p>
<i class="chat-panel panel-head-element bi-caret-down-fill" id="chat-panel-users-toggle"></i>
</div>
<div class="chat-panel" id="chat-panel-main-div">
<div class="chat-panel" id="chat-panel-multipanel-div">
</div>
<div class="chat-panel" id="chat-area">
<div class="chat-panel" id="chat-panel-buffer-div">
</div>
<div class="chat-panel" id="chat-panel-control-div">
<i class="chat-panel chat-panel-control bi-gear-fill" id="chat-panel-settings-icon"></i>
<i class="chat-panel chat-panel-control bi-magic" id="chat-panel-admin-icon"></i>
<i class="chat-panel chat-panel-control bi-images" id="chat-panel-emote-icon"></i>
<input class="chat-panel chat-panel-control" id="chat-panel-prompt" placeholder="Chat...">
<button class="chat-panel chat-panel-control" id="chat-panel-send-button">Send</button>
</div>
</div>
<div class="chat-panel" id="chat-panel-users-div">
<div class="drag-handle" id="chat-panel-users-drag-handle">
</div>
<div class="chat-panel" id="chat-panel-users-list-div">
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="/lib/socket.io/socket.io.min.js"></script>
<script src="/js/channel/chat.js"></script>
<script src="/js/channel/userlist.js"></script>
<script src="/js/channel/player.js"></script>
<script src="/js/channel/channel.js"></script>
</footer>
</html>

View file

@ -0,0 +1,37 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="/css/newChannel.css">
<title><%= instance %> - Channel Settings: <%= channel.name %></title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<form action="javascript:">
<span>
<label>Hidden:</label>
<input id="channel-hidden" type="checkbox" <% if(channel.settings.hidden){ %> checked <% } %>>
</span>
</form>
<a href="javascript:" id="channel-delete">Delete Channel</a>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="/js/channelSettings.js"></script>
</footer>
</body>
</html>

41
src/views/index.ejs Normal file
View file

@ -0,0 +1,41 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="css/index.css">
<title><%= instance %></title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<h3><a href="/newchannel">Start a new channel...</a></h3>
<div id="channel-guide-div" class="channel-guide">
<% chanGuide.forEach((channel) => { %>
<div id="channel-guide-entry-<%- channel.name %>" class="channel-guide-entry">
<a href="/c/<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><img id="channel-guide-entry-img-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item" src="<%- channel.thumbnail %>"></a>
<h3 id="channel-guide-entry-name-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><a href="/c/<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><%- channel.name %></a></h3>
<span id="channel-guide-entry-description-span-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item">
<p id="channel-guide-entry-description-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><%- channel.description %></h3>
</span>
</div>
<% }); %>
</div>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
</footer>
</html>

38
src/views/newChannel.ejs Normal file
View file

@ -0,0 +1,38 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="/css/newChannel.css">
<title><%= instance %> - New Channel</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<form action="javascript:">
<label>Channel Name:</label>
<input id="register-channel-name" placeholder="Required">
<label>Description:</label>
<input id="register-description" placeholder="Required">
<label>Thumbnail:</label>
<input id="register-thumbnail" placeholder="Required">
</form>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="js/newChannel.js"></script>
</footer>
</html>

View file

@ -0,0 +1,27 @@
<!--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/>.-->
<div id="navbar">
<p class="navbar-item" id="instance-title"><a href="/" class="navbar-item"><%= instance %></a></p>
<span class="navbar-item" id="right-controls">
<% if(user){ %>
<p class="navbar-item">Welcome, <a class="navbar-item" href="/profile"><%= user.user %></a> - <a href="/adminPanel" title="Admin Panel" class="bi bi-server navbar-item"></a> <a class="navbar-item" href="javascript:" id="logout-button">logout</a></p>
<% }else{ %>
<input class="navbar-item login-prompt" id="username-prompt" placeholder="username">
<input class="navbar-item login-prompt" id="password-prompt" placeholder="password" type="password">
<p class="navbar-item"><a class="navbar-item" href="javascript:" id="login-button">Login</a> - <a class="navbar-item" href="/register">Register</a></p>
<% } %>
</span>
</div>

View file

@ -0,0 +1,17 @@
<!--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/>.-->
<script src="/js/utils.js"></script>
<script src="/js/navbar.js"></script>

View file

@ -0,0 +1,18 @@
<!--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/>.-->
<link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css">
<link rel="stylesheet" type="text/css" href="/css/global.css">
<link rel="stylesheet" type="text/css" href="/css/theme/movie-night.css">

83
src/views/profile.ejs Normal file
View file

@ -0,0 +1,83 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<% var selfProfile = user ? profile ? profile.user == user.user : false : false %>
<link rel="stylesheet" type="text/css" href="/css/profile.css">
<% if(profile){ %>
<title><%= instance %> - Profile: <%= profile.user %></title>
<% } else { %>
<title><%= instance %> - Profile: Logged Out</title>
<% } %>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<% if(profile){ %>
<div class="profile" id="profile-div">
<h1 class="profile-item" id="profile-username"><%= profile.user %></h1>
<img class="profile-item" id="profile-img" src="<%= profile.img %>">
<% if(selfProfile){ %>
<p class="profile-item-edit">(<a class="profile-item-edit" id="profile-img-edit" href="javascript:">edit</a>)</p>
<% } %>
<p class="profile-item" id="profile-tokes">tokes: <%= profile.tokes %> (Not yet implemented)</p>
<span class="profile-item" id="profile-signature">
<p class="profile-item profile-item-label" id="profile-signature-label">Signature: <span class="profile-content" id="profile-signature-content"><%= profile.signature %></span></p>
<% if(selfProfile){ %>
<p class="profile-item-edit">(<a class="profile-item-edit" id="profile-signature-edit" href="javascript:">edit</a>)</p>
<% } %>
</span>
<span class="profile-item" id="profile-bio">
<p class="profile-item profile-item-label" id="profile-bio-label">Bio: <span class="profile-content" id="profile-bio-content"><%= profile.bio %></span></p>
<% if(selfProfile){ %>
<p class="profile-item-edit">(<a class="profile-item-edit" id="profile-bio-edit" href="javascript:">edit</a>)</p>
<% } %>
</span>
<p class="profile-item" id="profile-creation-date">Joined: <%= profile.date %></p>
<div class="profile-item" id="profile-badge-shelf">
<h3 class="profile-item" id="no-badge-label">Badgeless?</h3>
</div>
</div>
<% if(selfProfile){ %>
<div class="account-settings" id="account-settings-div">
<h3 class="account-settings" id="account-settings-label">Account Settings</h3>
<span class="account-settings-password-reset" id="account-settings-password-reset-div">
<h4 class="account-settings-password-reset" id="account-settings-password-reset-label">Password Reset:</h4>
<input class="account-settings-password-reset" id="account-settings-password-reset-old" placeholder="Current Password" type="password">
<input class="account-settings-password-reset" id="account-settings-password-reset-new" placeholder="New Password" type="password">
<input class="account-settings-password-reset" id="account-settings-password-reset-confirm" placeholder="Confirm New Password" type="password">
</span>
<span class="account-settings" id="account-settings-delete">
<a href="javascript:" class="account-settings" id="account-settings-delete-link">Delete Account</a>
</span>
</div>
<% } %>
<% }else if(user){ %>
<h1 class="profile-item" id="profile-error-label">Profile not found!</h1>
<% } else {%>
<h1 class="profile-item" id="profile-error-label">Please login to view your profile!</h1>
<% } %>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="/js/profile.js"></script>
</footer>
</html>

41
src/views/register.ejs Normal file
View file

@ -0,0 +1,41 @@
<!--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/>.-->
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<link rel="stylesheet" type="text/css" href="/css/register.css">
<title><%= instance %> - Account Registration</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<form action="javascript:">
<label>Username:</label>
<input id="register-username" placeholder="Required">
<label>Password:</label>
<input id="register-password" placeholder="Required" type="password">
<label>Confirm Password:</label>
<input id="register-password-confirm" placeholder="Required" type="password">
<label>Account Recovery Email:</label>
<input id="register-email" placeholder="Optional">
</form>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
<script src="js/register.js"></script>
</footer>
</html>