Added cli for user management, abstracted some database queries into handler functions.
parent
abcd6787ca
commit
68f9866c16
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
|
@ -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 = {
|
||||||
|
|
|
@ -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 };
|
|
@ -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) {
|
||||||
//only allow publish to public endpoint from localhost
|
|
||||||
//this is NOT a comprehensive way of doing this, but I'm ignoring it
|
|
||||||
//until satyr releases and someone opens an issue
|
|
||||||
if(session.ip.includes('127.0.0.1') || session.ip === '::1') {
|
if(session.ip.includes('127.0.0.1') || session.ip === '::1') {
|
||||||
|
//only allow publish to public endpoint from localhost
|
||||||
|
//this is NOT a comprehensive way of doing this, but I'm ignoring it
|
||||||
|
//until satyr releases and someone opens an issue
|
||||||
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);
|
||||||
|
|
Reference in New Issue