Files
zoff/server/routing/client/api.js
Kasper Rynning-Tønnesen 9839c0bdda Timeout api, and fixes it lead to
- timeout on api-calls with Retry-After present in header
- Fixed issue where when server restarts you are sometimes logged out for some reason
- README updates
2018-03-01 14:55:23 +01:00

548 lines
23 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var express = require('express');
var router = express.Router();
var path = require('path');
router.use(function(req, res, next) {
next(); // make sure we go to the next routes and don't stop here
});
router.route('/api/frontpages').get(function(req, res) {
db.collection("frontpage_lists").find({frontpage: true, count: {$gt: 0}}, function(err, docs) {
db.collection("connected_users").find({"_id": "total_users"}, function(err, tot) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({channels: docs, viewers: tot[0].total_users.length}));
});
});
});
router.route('/api/generate_name').get(function(req, res) {
Functions.generate_channel_name(res);
});
router.route('/api/list/:channel_name/:video_id').delete(function(req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') ||
!req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id')) {
res.sendStatus(400);
return;
}
try {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]);
var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex"));
req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex");
var userpass = req.body.userpass;
var channel_name = cleanChannelName(req.params.channel_name);
var video_id = req.params.video_id;
if(typeof(userpass) != "string" || typeof(adminpass) != "string") {
throw "Wrong format";
}
} catch(e) {
res.sendStatus(400);
return;
}
validateLogin(adminpass, userpass, channel_name, "delete", res, function(exists) {
if(!exists) {
res.sendStatus(404);
return;
}
db.collection(channel_name).find({id:video_id, now_playing: false}, function(err, docs){
if(docs.length == 0) {
res.sendStatus(404);
return;
}
dont_increment = true;
if(docs[0]){
if(docs[0].type == "suggested"){
dont_increment = false;
}
db.collection(channel_name).remove({id:video_id}, function(err, docs){
io.to(channel_name).emit("channel", {type:"deleted", value: video_id});
if(dont_increment) {
db.collection("frontpage_lists").update({_id: channel_name, count: {$gt: 0}}, {$inc: {count: -1}, $set:{accessed: Functions.get_time()}}, {upsert: true}, function(err, docs){
res.sendStatus(200);
return;
});
} else {
res.sendStatus(200);
return;
}
});
}
});
});
});
function cleanChannelName(channel_name) {
var coll = emojiStrip(channel_name).toLowerCase();
coll = coll.replace("_", "");
coll = encodeURIComponent(coll).replace(/\W/g, '');
coll = filter.clean(coll);
return coll;
}
function validateLogin(adminpass, userpass, channel_name, type, res, callback) {
db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) {
var exists = false;
if(conf.length > 0 && ((conf[0].userpass == undefined || conf[0].userpass == "" || conf[0].userpass == userpass))) {
exists = true;
} else if(conf.length > 0) {
res.sendStatus(403);
return;
}
if(
(type == "add" && ((conf[0].addsongs && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) || !conf[0].addsongs)) ||
(type == "delete" && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) ||
(type == "vote" && ((conf[0].vote && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) || !conf[0].vote)) ||
(type == "config" && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass))
) {
callback(exists);
} else {
res.sendStatus(403);
return;
}
});
}
router.route('/api/conf/:channel_name').put(function(req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') ||
!req.params.hasOwnProperty('channel_name') || !req.body.hasOwnProperty('voting') ||
!req.body.hasOwnProperty('addsongs') || !req.body.hasOwnProperty('longsongs') ||
!req.body.hasOwnProperty('frontpage') || !req.body.hasOwnProperty('allvideos') ||
!req.body.hasOwnProperty('skipping') || !req.body.hasOwnProperty('shuffling') ||
!req.body.hasOwnProperty('userpass_changed')) {
res.sendStatus(400);
return;
}
try {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]);
var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex"));
req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex");
var userpass = req.body.userpass;
var voting = req.body.voting;
var addsongs = req.body.addsongs;
var longsongs = req.body.longsongs;
var frontpage = req.body.frontpage;
var allvideos = req.body.allvideos;
var removeplay = req.body.removeplay;
var skipping = req.body.skipping;
var shuffling = req.body.shuffling;
var userpass_changed = req.body.userpass_changed;
var channel_name = cleanChannelName(req.params.channel_name);
if(typeof(userpass) != "string" || typeof(adminpass) != "string" ||
typeof(voting) != "boolean" || typeof(addsongs) != "boolean" ||
typeof(longsongs) != "boolean" || typeof(frontpage) != "boolean" ||
typeof(allvideos) != "boolean" || typeof(removeplay) != "boolean" ||
typeof(skipping) != "boolean" || typeof(shuffling) != "boolean" ||
typeof(userpass_changed) != "boolean") {
throw "Wrong format";
}
} catch(e) {
res.send(e);
res.sendStatus(400);
return;
}
db.collection("timeout_api").find({
type: "DELETE",
guid: guid,
}, function(err, docs) {
if(docs.length > 0) {
var date = new Date(docs[0].createdAt);
date.setSeconds(date.getSeconds() + 5);
var now = new Date();
var retry_in = (date.getTime() - now.getTime()) / 1000;
res.header({'Retry-After': retry_in});
res.sendStatus(429);
return;
}
validateLogin(adminpass, userpass, channel_name, "config", res, function(exists) {
if(!exists) {
res.sendStatus(404);
return;
}
if((!userpass_changed && frontpage) || (userpass_changed && userpass == "")) {
userpass = "";
} else if(userpass_changed && userpass != "") {
frontpage = false;
}
var description = "";
var obj = {
addsongs:addsongs,
allvideos:allvideos,
frontpage:frontpage,
skip:skipping,
vote:voting,
removeplay:removeplay,
shuffle:shuffling,
longsongs:longsongs,
adminpass:adminpass,
desc: description,
};
if(userpass_changed) {
obj["userpass"] = userpass;
} else if (frontpage) {
obj["userpass"] = "";
}
db.collection(channel_name + "_settings").update({views:{$exists:true}}, {
$set:obj
}, function(err, docs){
if(obj.adminpass !== "") obj.adminpass = true;
if(obj.hasOwnProperty("userpass") && obj.userpass != "") obj.userpass = true;
else obj.userpass = false;
io.to(channel_name).emit("conf", [obj]);
db.collection("frontpage_lists").update({_id: channel_name}, {$set:{
frontpage:frontpage, accessed: Functions.get_time()}
},
{upsert:true}, function(err, docs){
db.collection("timeout_api").update({type: "DELETE", guid: guid}, {
$set: {
"createdAt": new Date(),
type: "DELETE",
guid: guid,
},
}, {upsert: true}, function(err, docs) {
res.header({'Content-Type': 'application/json'});
res.status(200).send(JSON.stringify(obj));
});
});
});
});
});
})
router.route('/api/list/:channel_name/:video_id').put(function(req,res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') ||
!req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id')) {
res.sendStatus(400);
return;
}
try {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]);
var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex"));
req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex");
var userpass = req.body.userpass;
var channel_name = cleanChannelName(req.params.channel_name);
var video_id = req.params.video_id;
if(typeof(userpass) != "string" || typeof(adminpass) != "string") {
throw "Wrong format";
}
} catch(e) {
res.send(e);
res.sendStatus(400);
return;
}
db.collection("timeout_api").find({
type: "PUT",
guid: guid,
}, function(err, docs) {
if(docs.length > 0) {
var date = new Date(docs[0].createdAt);
date.setSeconds(date.getSeconds() + 5);
var now = new Date();
var retry_in = (date.getTime() - now.getTime()) / 1000;
res.header({'Retry-After': retry_in});
res.sendStatus(429);
return;
}
validateLogin(adminpass, userpass, channel_name, "vote", res, function(exists) {
if(!exists) {
res.sendStatus(404);
return;
}
db.collection(channel_name).find({id: video_id, now_playing: false}, function(err, song) {
if(song.length == 0) {
res.sendStatus(404);
return;
} else if(song[0].guids.indexOf(guid) > -1) {
res.sendStatus(409);
return;
} else {
song[0].votes += 1;
song[0].guids.push(guid);
db.collection(channel_name).update({id: video_id}, {$inc:{votes:1}, $set:{added:Functions.get_time()}, $push :{guids: guid}}, function(err, success) {
io.to(channel_name).emit("channel", {type: "vote", value: video_id, time: Functions.get_time()});
List.getNextSong(channel_name, function() {
db.collection("timeout_api").update({type: "PUT", guid: guid}, {
$set: {
"createdAt": new Date(),
type: "PUT",
guid: guid,
},
}, {upsert: true}, function(err, docs) {
res.header({'Content-Type': 'application/json'});
res.status(200).send(JSON.stringify(song[0]));
return;
});
});
});
}
})
});
});
});
router.route('/api/list/:channel_name/:video_id').post(function(req,res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') ||
!req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id') ||
!req.body.hasOwnProperty('duration') || !req.body.hasOwnProperty('start_time') ||
!req.body.hasOwnProperty('end_time') || !req.body.hasOwnProperty('title')) {
res.sendStatus(400);
return;
}
try {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]);
var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex"));
req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex");
var userpass = req.body.userpass;
var channel_name = cleanChannelName(req.params.channel_name);
var video_id = req.params.video_id;
var duration = parseInt(req.body.duration);
var start_time = parseInt(req.body.start_time);
var end_time = parseInt(req.body.end_time);
if(duration != end_time - start_time) duration = end_time - start_time;
var title = req.body.title;
if(typeof(userpass) != "string" || typeof(adminpass) != "string" ||
typeof(title) != "string") {
throw "Wrong format";
}
} catch(e) {
res.send(e);
res.sendStatus(400);
return;
}
db.collection("timeout_api").find({
type: "POST",
guid: guid,
}, function(err, docs) {
if(docs.length > 0) {
var date = new Date(docs[0].createdAt);
date.setSeconds(date.getSeconds() + 5);
var now = new Date();
var retry_in = (date.getTime() - now.getTime()) / 1000;
res.header({'Retry-After': retry_in});
res.sendStatus(429);
return;
}
validateLogin(adminpass, userpass, channel_name, "add", res, function(exists) {
db.collection(channel_name).find({id: video_id}, function(err, result) {
if(result.length == 0) {
db.collection(channel_name).find({now_playing: true}, function(err, now_playing) {
var set_np = false;
if(now_playing.length == 0) {
set_np = true;
}
var new_song = {"added": Functions.get_time(),"guids":[guid],"id":video_id,"now_playing":set_np,"title":title,"votes":1, "duration":duration, "start": parseInt(start_time), "end": parseInt(end_time)};
db.collection("frontpage_lists").find({"_id": channel_name}, function(err, count) {
var create_frontpage_lists = false;
if(count.length == 0) {
create_frontpage_lists = true;
}
if(!exists) {
var configs = {"addsongs":false, "adminpass":"", "allvideos":true, "frontpage":true, "longsongs":false, "removeplay": false, "shuffle": true, "skip": false, "skips": [], "startTime":Functions.get_time(), "views": [], "vote": false, "desc": ""};
db.collection(channel_name + "_settings").insert(configs, function(err, docs){
io.to(channel_name).emit("conf", configs);
});
}
db.collection(channel_name).insert(new_song, function(err, success) {
if(create_frontpage_lists) {
db.collection("frontpage_lists").insert({"_id": channel_name, "count" : 1, "frontpage": true, "accessed": Functions.get_time(), "viewers": 1}, function(err, docs) {
postEnd(channel_name, configs, new_song, guid, res);
});
} else if(set_np) {
Frontpage.update_frontpage(channel_name, video_id, title, function() {
io.to(channel_name).emit("np", new_song);
postEnd(channel_name, configs, new_song, guid, res);
});
} else {
db.collection("frontpage_lists").update({"_id": channel_name}, {$inc: {count: 1}}, function(err, docs) {
io.to(channel_name).emit("channel", {type: "added", value: new_song});
postEnd(channel_name, configs, new_song, guid, res);
});
}
});
})
})
} else {
res.sendStatus(409);
return;
}
});
});
});
});
function postEnd(channel_name, configs, new_song, guid, res) {
io.to(channel_name).emit("conf", configs);
io.to(channel_name).emit("channel", {type: "added", value: new_song});
List.getNextSong(channel_name, function() {
db.collection("timeout_api").update({type: "POST", guid: guid}, {
$set: {
"createdAt": new Date(),
type: "POST",
guid: guid,
},
}, {upsert: true}, function(err, docs) {
res.header({'Content-Type': 'application/json'});
res.status(200).send(JSON.stringify(new_song));
return;
});
});
}
router.route('/api/list/:channel_name').get(function(req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
var channel_name = req.params.channel_name;
db.collection(channel_name).find({views: {$exists: false}}, {
start: 1,
end: 1,
added: 1,
id: 1,
title: 1,
votes: 1,
duration: 1,
type: 1,
_id: 0,
now_playing: 1,
}, function(err, docs) {
if(docs.length > 0) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(docs));
} else {
/*res.status(404);
res.send(404);*/
res.status(404).redirect("/404");
}
});
});
router.route('/api/conf/:channel_name').get(function(req, res) {
var channel_name = req.params.channel_name;
db.collection(channel_name + "_settings").find({views: {$exists: true}}, {
addsongs: 1,
adminpass: 1,
allvideos: 1,
frontpage: 1,
longsongs: 1,
removeplay: 1,
shuffle: 1,
skip: 1,
startTime: 1,
userpass: 1,
vote: 1,
_id: 0
}, function(err, docs) {
if(docs.length > 0) {
var conf = docs[0];
if(conf.adminpass != "") {
conf.adminpass = true;
} else {
conf.adminpass = false;
}
if(conf.userpass != "") {
conf.userpass = true;
} else {
conf.userpass = false;
}
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(conf));
} else {
/*res.status(404);
res.send(404);*/
res.status(404).redirect("/404");
}
});
});
router.route('/api/imageblob').post(function(req, res) {
var Jimp = require("jimp");
Jimp.read('https://img.youtube.com/vi/' + req.body.id + '/mqdefault.jpg', function (err, image) {
if (err) console.log(err);
image.blur(50)
.write(path.join(pathThumbnails, '/public/assets/images/thumbnails/' + req.body.id + '.jpg'), function(e, r) {
res.send(req.body.id + ".jpg");
});
});
});
var nodemailer = require('nodemailer');
try {
var mailconfig = require(path.join(__dirname, '../../config/mailconfig.js'));
var recaptcha_config = require(path.join(__dirname, '../../config/recaptcha.js'));
var Recaptcha = require('express-recaptcha');
var RECAPTCHA_SITE_KEY = recaptcha_config.site;
var RECAPTCHA_SECRET_KEY = recaptcha_config.key;
var recaptcha = new Recaptcha(RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY);
router.route('/api/mail').post(recaptcha.middleware.verify, function(req, res) {
if(req.recaptcha.error == null) {
let transporter = nodemailer.createTransport(mailconfig);
transporter.verify(function(error, success) {
if (error) {
res.sendStatus(500);
return;
} else {
var subject = 'ZOFF: Contact form webpage';
if(req.body.error_report) {
subject = 'ZOFF: Error report';
}
var from = req.body.from;
var message = req.body.message;
var msg = {
from: mailconfig.from,
to: mailconfig.to,
subject: subject,
text: message,
html: message,
replyTo: from
}
transporter.sendMail(msg, (error, info) => {
if (error) {
res.send("failed");
transporter.close();
return;
}
res.send("success");
transporter.close();
});
}
});
} else {
res.send("failed");
return;
}
});
} catch(e) {
console.log("Mail is not configured and wont work");
console.log("Seems you forgot to create a mailconfig.js in /server/config/. Have a look at the mailconfig.example.js.");
router.route('/api/mail').post(function(req, res) {
console.log("Someone tried to send a mail, but the mailsystem hasn't been enabled..")
res.send("failed");
return;
});
}
module.exports = router;