I don't even know.

Filled out API for user management
Proper escaping of user input for SQL queries (stream keys aren't user input)
Filled out frontend with profile management, vods, etc.
I don't remember there's probably more, fuck.
This commit is contained in:
knotteye
2019-10-05 14:34:57 -05:00
parent eefa7c8dc7
commit f9e4a7a5c6
25 changed files with 323 additions and 104 deletions

View File

@ -5,35 +5,44 @@ function init(conf: object){
config = conf;
}
async function register(name: string, password: string, streamer: boolean) {
if(!config.registration){
return {"error":"registration disabled"};
}
else {
if(name.includes(';') || name.includes(' ')) return {"error":"illegal characters"};
let s: boolean;
if(streamer && config.streamKeys) s = true;
else s = false;
let r: boolean = await db.addUser(name, password, s, false);
if(r) return {"success":""};
else return {"error":""};
async function register(name: string, password: string, confirm: string) {
if(!config.registration) return {"error":"registration disabled"};
if(name.includes(';') || name.includes(' ') || name.includes('\'')) return {"error":"illegal characters"};
if(password !== confirm) return {"error":"mismatched passwords"};
for(let i=0;i<config.restrictedNames.length;i++){
if (name === config.restrictedNames[i]) return {"error":"restricted name"};
}
let r: boolean = await db.addUser(name, password);
if(r) return {"success":""};
return {"error":""};
}
async function login(name: string, pass: string) {
return await db.validatePassword(name, pass);
async function update(name: string, password: string, title: string, bio: string, record: boolean){
if(!name || !password) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"};
await db.query('UPDATE user_meta set title='+db.raw.escape(title)+', about='+db.raw.escape(bio)+' where username='+db.raw.escape(name));
if(!record) await db.query('UPDATE users set record_flag=false where username='+db.raw.escape(name));
else await db.query('UPDATE users set record_flag=true where username='+db.raw.escape(name));
return {"success":""};
}
async function users(num: number) {
return await db.query('select username from users limit '+num);
async function changepwd(name: string, password: string, newpwd: string){
if(!name || !password) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"};
let newhash: string = await db.hash(newpwd);
await db.query('UPDATE users set password_hash='+db.raw.escape(newhash)+'where username='+db.raw.escape(name)+' limit 1');
return {"success":""};
}
async function user(name: string) {
async function changesk(name: string, password: string){
if(!name || !password) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"};
let key: string = await db.genKey();
await db.query('UPDATE users set stream_key='+db.raw.escape(key)+'where username='+db.raw.escape(name)+' limit 1');
return {"success":key};
}
async function instance() {
}
export { init, register };
export { init, register, update, changepwd, changesk };

View File

@ -6,8 +6,6 @@ db.init(config.database, config.bcrypt);
flags.defineString('add', '', 'User to add');
flags.defineString('remove', '', 'User to remove');
flags.defineString('mkstreamer', '', 'Give a stream key to a user');
flags.defineString('rmstreamer', '', 'Remove a stream key from a user');
flags.defineString('password', '', 'password to hash');
flags.defineBoolean('admin');
flags.defineBoolean('streamer');
@ -15,7 +13,7 @@ flags.defineBoolean('streamer');
flags.parse();
if(flags.get('add') !== ''){
db.addUser(flags.get('add'), flags.get('password'), flags.get('streamer'), flags.get('admin')).then((result) => {
db.addUser(flags.get('add'), flags.get('password')).then((result) => {
if(result) console.log("User added successfully.");
else console.log("Could not add user. Is the password field empty?");
process.exit();
@ -28,20 +26,4 @@ if(flags.get('remove') !== ''){
else console.log("Could not remove user.");
process.exit();
});
}
if(flags.get('mkstreamer') !== ''){
db.addStreamKey(flags.get('mkstreamer')).then((result) => {
if(result) console.log("Key added successfully.");
else console.log("Could not add key.");
process.exit();
});
}
if(flags.get('rmstreamer') !== ''){
db.rmStreamKey(flags.get('rmstreamer')).then((result) => {
if(result) console.log("Key removed successfully.");
else console.log("Could not remove key.");
process.exit();
});
}

View File

@ -11,13 +11,14 @@ function run(): void{
const satyr: object = {
privateEndpoint: config.media.privateEndpoint,
record: config.media.record,
streamKeys: config.media.streamKeys,
registration: config.satyr.registration,
webFormat: config.satyr.webFormat,
restrictedNames: config.satyr.restrictedNames,
name: config.satyr.name,
domain: config.satyr.domain,
email: config.satyr.email
email: config.satyr.email,
rootredirect: config.satyr.rootredirect,
version: process.env.npm_package_version
};
const nms: object = {
logType: config.server.logs,

View File

@ -10,24 +10,23 @@ function init (db: object, bcrypt: object){
cryptoconfig = bcrypt;
}
async function addUser(name: string, password: string, streamer: boolean, admin: boolean){
async function addUser(name: string, password: string){
//does not respect registration setting in config
//nor stream keys
if(password === '') return false;
let key: string = ' ';
if (streamer) key = await genKey();
let key: string = await genKey();
let hash: string = await bcrypt.hash(password, cryptoconfig.saltRounds);
let dupe = await query('select * from users where username=\''+name+'\'');
let dupe = await query('select * from users where username='+raw.escape(name));
if(dupe[0]) return false;
let q: string = 'INSERT INTO users (username, password_hash, stream_key, record_flag, is_mod) VALUES (\''+name+'\', \''+hash+'\', \''+key+'\', 0, '+admin+')';
await query(q);
await query('INSERT INTO users (username, password_hash, stream_key, record_flag) VALUES ('+raw.escape(name)+', '+raw.escape(hash)+', '+raw.escape(key)+', 0');
await query('INSERT INTO user_meta (username, title, about, live) VALUES ('+raw.escape(name)+',\'\',\'\',false)');
return true;
}
async function rmUser(name: string){
let exist = await query('select * from users where username=\''+name+'\'');
let exist = await query('select * from users where username='+raw.escape(name));
if(!exist[0]) return false;
await query('delete from users where username=\''+name+'\' limit 1');
await query('delete from users where username='+raw.escape(name)+' limit 1');
await query('delete from user_meta where username='+raw.escape(name)+' limit 1');
return true;
}
@ -38,21 +37,6 @@ async function genKey(){
else return key;
}
async function addStreamKey(name: string){
let exist = await query('select * from users where username=\''+name+'\'');
if(!exist[0]) return false;
let key = await genKey();
await query('update users set stream_key=\''+key+'\' where username=\''+name+'\' limit 1');
return true;
}
async function rmStreamKey(name: string){
let exist = await query('select * from users where username=\''+name+'\'');
if(!exist[0]) return false;
await query('update users set stream_key=\'\' where username=\''+name+'\' limit 1');
return true;
}
async function query(query: string){
return new Promise(resolve => raw.query(query, (error, results, fields) => {
if(error) throw error;
@ -61,8 +45,12 @@ async function query(query: string){
}
async function validatePassword(username: string, password: string){
let pass: any= await query('select password from users where username=\''+username+'\' limit 1');
return await bcrypt.compare(password, pass[0].password_hash);
let pass: any = await query('select password_hash from users where username='+raw.escape(username)+' limit 1');
return await bcrypt.compare(password, pass[0].password_hash.toString());
}
export { query, raw, init, addUser, rmUser, addStreamKey, rmStreamKey, validatePassword };
async function hash(pwd){
return await bcrypt.hash(pwd, cryptoconfig.saltRounds);
}
export { query, raw, init, addUser, rmUser, validatePassword, hash, genKey };

View File

@ -1,6 +1,7 @@
import * as express from "express";
import * as njk from "nunjucks";
import * as bodyparser from "body-parser";
import * as fs from "fs";
import * as api from "./api";
import * as db from "./database";
@ -18,30 +19,95 @@ function init(satyr: any){
sitename: satyr.name,
domain: satyr.domain,
email: satyr.email,
user: '',
streamtitle: '',
rootredirect: satyr.rootredirect,
version: satyr.version
};
app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: true }));
//site handlers
app.get('/', (req, res) => {
res.render('index.njk', njkconf);
res.redirect(njkconf.rootredirect);
});
app.get('/about', (req, res) => {
res.render('about.njk', njkconf);
});
app.get('/users/*', (req, res) => {
njkconf.user = req.url.split('/')[2].toLowerCase();
res.render('user.njk', njkconf);
});
app.get('/registration', (req, res) => {
res.render('registration.njk', njkconf);
});
app.post('/api/register', (req, res) => {
api.register(req.body.username, req.body.password, req.body.streamer).then( (result) => {
res.send({"error":""});
app.get('/users', (req, res) => {
db.query('select username from users').then((result) => {
njkconf.list = result;
res.render('list.njk', njkconf);
njkconf.list = '';
});
});
app.get('/users/live', (req, res) => {
db.query('select username,title from user_meta where live=1;').then((result) => {
njkconf.list = result;
res.render('live.njk', njkconf);
njkconf.list = '';
});
});
app.get('/users/*', (req, res) => {
njkconf.user = req.url.split('/')[2].toLowerCase();
db.query('select title,about from user_meta where username='+db.raw.escape(njkconf.user)).then((result) => {
if(result[0]){
njkconf.streamtitle = result[0].title;
njkconf.about = result[0].about;
res.render('user.njk', njkconf);
}
else res.render('404.njk', njkconf);
});
});
app.get('/vods/*', (req, res) => {
njkconf.user = req.url.split('/')[2].toLowerCase();
db.query('select username from user_meta where username='+db.raw.escape(njkconf.user)).then((result) => {
if(result[0]){
fs.readdir('./site/live/'+njkconf.user, {withFileTypes: true} , (err, files) => {
if(files) njkconf.list = files.filter(fn => fn.name.endsWith('.mp4'));
else njkconf.list = [];
res.render('vods.njk', njkconf);
});
}
else res.render('404.njk', njkconf);
});
});
app.get('/register', (req, res) => {
res.render('registration.njk', njkconf);
});
app.get('/profile', (req, res) => {
res.render('profile.njk', njkconf);
});
app.get('/changepwd', (req, res) => {
res.render('changepwd.njk', njkconf);
});
app.get('/changesk', (req, res) => {
res.render('changesk.njk', njkconf);
});
//api handlers
app.post('/api/register', (req, res) => {
api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {
res.send(result);
});
});
app.post('/api/user', (req, res) => {
api.update(req.body.username, req.body.password, req.body.title, req.body.bio, req.body.record).then((result) => {
res.send(result);
});
});
app.post('/api/user/password', (req, res) => {
api.changepwd(req.body.username, req.body.password, req.body.newpassword).then((result) => {
res.send(result);
});
});
app.post('/api/user/streamkey', (req, res) => {
api.changesk(req.body.username, req.body.password).then((result) => {
res.send(result);
})
});
//static files if nothing else matches first
app.use(express.static('site'));
//404 Handler
app.use(function (req, res, next) {
res.status(404).render('404.njk', njkconf);
});
}
export { init };

View File

@ -8,7 +8,7 @@ function init (mediaconfig: any, satyrconfig: any) {
nms.run();
nms.on('postPublish', (id, StreamPath, args) => {
console.log("[NodeMediaServer] Prepublish Hook for stream:",id);
console.log("[NodeMediaServer] Publish Hook for stream:",id);
let session = nms.getSession(id);
let app: string = StreamPath.split("/")[1];
let key: string = StreamPath.split("/")[2];
@ -31,6 +31,8 @@ function init (mediaconfig: any, satyrconfig: any) {
return false;
}
console.log("[NodeMediaServer] Public endpoint, checking record flag.");
//set live flag
db.query('update user_meta set live=true where username=\''+key+'\' limit 1');
//if this stream is from the public endpoint, check if we should be recording
return db.query('select username,record_flag from users where username=\''+key+'\' limit 1').then((results) => {
if(results[0].record_flag && satyrconfig.record){
@ -61,7 +63,11 @@ function init (mediaconfig: any, satyrconfig: any) {
//if the url is formatted correctly and the user is streaming to the correct private endpoint
//grab the username from the database and redirect the stream there if the key is valid
//otherwise kill the session
db.query('select username from users where stream_key=\''+key+'\' limit 1').then((results) => {
if(key.includes(' ')) {
session.reject();
return false;
}
db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then((results) => {
if(results[0]){
exec('ffmpeg -analyzeduration 0 -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username);
console.log('[NodeMediaServer] Stream key okay for stream:',id);
@ -72,6 +78,13 @@ function init (mediaconfig: any, satyrconfig: any) {
}
});
});
nms.on('donePublish', (id, StreamPath, args) => {
let app: string = StreamPath.split("/")[1];
let key: string = StreamPath.split("/")[2];
if(app === mediaconfig.trans.tasks[0].app) {
db.query('update user_meta set live=false where username=\''+key+'\' limit 1');
}
});
nms.on('prePlay', (id, StreamPath, args) => {
let session = nms.getSession(id);
let app: string = StreamPath.split("/")[1];