Added cli for user management, abstracted some database queries into handler functions.

merge-requests/1/merge
knotteye 2019-09-24 17:29:37 -05:00
parent abcd6787ca
commit 68f9866c16
6 changed files with 124 additions and 40 deletions

11
package-lock.json generated
View File

@ -354,6 +354,11 @@
} }
} }
}, },
"flags": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/flags/-/flags-0.1.3.tgz",
"integrity": "sha1-lh0vyM3zZp1jBB4w5bJK2tNvV1g="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -1017,6 +1022,12 @@
"mime-types": "~2.1.24" "mime-types": "~2.1.24"
} }
}, },
"typescript": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"dev": true
},
"unpipe": { "unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -6,7 +6,8 @@
"author": "knotteye", "author": "knotteye",
"scripts": { "scripts": {
"start": "node build/controller.js", "start": "node build/controller.js",
"build": "tsc" "build": "tsc",
"user": "node build/cli.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -15,12 +16,14 @@
"dependencies": { "dependencies": {
"bcrypt": "^3.0.6", "bcrypt": "^3.0.6",
"config": "^3.2.2", "config": "^3.2.2",
"flags": "^0.1.3",
"irc": "^0.5.2", "irc": "^0.5.2",
"mysql": "^2.17.1", "mysql": "^2.17.1",
"node-media-server": ">=2.1.3 <3.0.0", "node-media-server": ">=2.1.3 <3.0.0",
"toml": "^3.0.0" "toml": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.7.5" "@types/node": "^12.7.5",
"typescript": "^3.6.3"
} }
} }

47
src/cli.ts Normal file
View File

@ -0,0 +1,47 @@
import * as db from "./database"
import * as flags from "flags";
import * as config from "config"
db.run(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');
flags.parse();
if(flags.get('add') !== ''){
db.addUser(flags.get('add'), flags.get('password'), flags.get('streamer'), flags.get('admin')).then((result) => {
if(result) console.log("User added successfully.");
else console.log("Could not add user. Is the password field empty?");
process.exit();
});
}
if(flags.get('remove') !== ''){
db.rmUser(flags.get('remove')).then((result) => {
if(result) console.log("User removed successfully.");
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

@ -3,23 +3,7 @@ import * as ircd from "./ircd";
import * as db from "./database"; import * as db from "./database";
const config = require('config'); const config = require('config');
/*var dbcfg: object;
var servercfg: object;
var bcryptcfg: object;
var satyrcfg: object;
var ircdcfg: object;
function init(): void{
dbcfg = config.get('database');
bcryptcfg = config.get('bcrypt');
servercfg = config.get('server');
satyrcfg = config.get('satyr');
ircdcfg = config.get('ircd');
}*/
function run(): void{ function run(): void{
//init();
const dbcfg = config.database; const dbcfg = config.database;
const bcryptcfg = config.bcrypt; const bcryptcfg = config.bcrypt;
const satyr: object = { const satyr: object = {

View File

@ -1,26 +1,67 @@
import * as mysql from "mysql"; import * as mysql from "mysql";
import * as bcrypt from "bcrypt"; import * as bcrypt from "bcrypt";
import * as crypto from "crypto";
import { resolve } from "url";
var raw: any; var raw: any;
var cryptoconfig: object; var cryptoconfig: any;
function run (db: object, bcrypt: object){ function run (db: object, bcrypt: object){
raw = mysql.createPool(db); raw = mysql.createPool(db);
cryptoconfig = bcrypt; cryptoconfig = bcrypt;
} }
function streamKeyAuth(key: string){ async function addUser(name: string, password: string, streamer: boolean, admin: boolean){
; //does not respect registration setting in config
if(password === '') return false;
let key: string = ' ';
if (streamer) key = await genKey();
let hash: string = await bcrypt.hash(password, cryptoconfig.saltRounds);
let dupe = await query('select * from users where username=\''+name+'\'');
if(dupe[0]) return false;
//INSERT INTO users (username, password_hash, stream_key, record_flag, is_mod) VALUES ('name', 'hash', 'key', 0, admin);
let q: string = 'INSERT INTO users (username, password_hash, stream_key, record_flag, is_mod) VALUES (\''+name+'\', \''+hash+'\', \''+key+'\', 0, '+admin+')';
await query(q);
return true;
}
async function rmUser(name: string){
let exist = await query('select * from users where username=\''+name+'\'');
if(!exist[0]) return false;
await query('delete from users where username=\''+name+'\' limit 1');
return true;
}
async function genKey(){
let key: string = crypto.randomBytes(10).toString('hex');
let result = await query('select * from users where stream_key=\''+key+'\'');
if(result[0]) return await 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;
resolve(results);
}));
} }
async function validatePassword(username: string, password: string){ async function validatePassword(username: string, password: string){
raw.connect(); ;
return raw.query('select password_hash from users where username=\''+username+'\' limit 1', (error, results, fields) => {
if (error) { throw error; }
return bcrypt.compare(password, results[0].password_hash, (err, result) =>{
if (err) { throw err; }
return result;
});
})
} }
export { streamKeyAuth, validatePassword, raw, run }; export { query, raw, run, addUser, rmUser, addStreamKey, rmStreamKey };

View File

@ -1,7 +1,6 @@
import * as NodeMediaServer from "node-media-server"; import * as NodeMediaServer from "node-media-server";
import { mkdir } from "fs"; import { mkdir } from "fs";
import * as db from "./database"; import * as db from "./database";
//import { transcode } from "buffer"; why is this here?
const { exec } = require('child_process'); const { exec } = require('child_process');
function boot (mediaconfig: any, satyrconfig: any) { function boot (mediaconfig: any, satyrconfig: any) {
@ -20,20 +19,20 @@ function boot (mediaconfig: any, satyrconfig: any) {
return false; return false;
} }
if(app === mediaconfig.trans.tasks[0].app) { if(app === mediaconfig.trans.tasks[0].app) {
if(session.ip.includes('127.0.0.1') || session.ip === '::1') {
//only allow publish to public endpoint from localhost //only allow publish to public endpoint from localhost
//this is NOT a comprehensive way of doing this, but I'm ignoring it //this is NOT a comprehensive way of doing this, but I'm ignoring it
//until satyr releases and someone opens an issue //until satyr releases and someone opens an issue
if(session.ip.includes('127.0.0.1') || session.ip === '::1') {
console.log("[NodeMediaServer] Local publish, stream:",`${id} ok.`); console.log("[NodeMediaServer] Local publish, stream:",`${id} ok.`);
} }
else{ else{
console.log("[NodeMediaServer] Non-local Publish to public endpoint, rejecting stream:",id); console.log("[NodeMediaServer] Non-local Publish to public endpoint, rejecting stream:",id);
session.reject(); session.reject();
return false;
} }
console.log("[NodeMediaServer] Public endpoint, checking record flag."); console.log("[NodeMediaServer] Public endpoint, checking record flag.");
//if this stream is from the public endpoint, stream //if this stream is from the public endpoint, check if we should be recording
db.raw.query('select username from users where username=\''+key+'\' and record_flag=true limit 1', (error, results, fields) => { return db.query('select username from users where username=\''+key+'\' and record_flag=true limit 1').then((results) => {
if (error) {throw error;}
if(results[0].username && satyrconfig.record){ if(results[0].username && satyrconfig.record){
console.log('[NodeMediaServer] Initiating recording for stream:',id); console.log('[NodeMediaServer] Initiating recording for stream:',id);
mkdir(mediaconfig.http.mediaroot+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username, { recursive : true }, (err) => { mkdir(mediaconfig.http.mediaroot+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username, { recursive : true }, (err) => {
@ -50,8 +49,8 @@ function boot (mediaconfig: any, satyrconfig: any) {
else { else {
console.log('[NodeMediaServer] Skipping recording for stream:',id); console.log('[NodeMediaServer] Skipping recording for stream:',id);
} }
});
return true; return true;
});
} }
if(app !== satyrconfig.privateEndpoint){ if(app !== satyrconfig.privateEndpoint){
//app isn't at public endpoint if we've reached this point //app isn't at public endpoint if we've reached this point
@ -62,8 +61,7 @@ function boot (mediaconfig: any, satyrconfig: any) {
//if the url is formatted correctly and the user is streaming to the correct private endpoint //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 //grab the username from the database and redirect the stream there if the key is valid
//otherwise kill the session //otherwise kill the session
db.raw.query('select username from users where stream_key=\''+key+'\' limit 1', (error, results, fields) => { db.query('select username from users where stream_key=\''+key+'\' limit 1').then((results) => {
if (error) {throw error;}
if(results[0]){ 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); 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); console.log('[NodeMediaServer] Stream key okay for stream:',id);