diff --git a/package-lock.json b/package-lock.json index 4b95026..c3a19be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -354,6 +354,11 @@ } } }, + "flags": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/flags/-/flags-0.1.3.tgz", + "integrity": "sha1-lh0vyM3zZp1jBB4w5bJK2tNvV1g=" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -1017,6 +1022,12 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 7e5a059..2015eab 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "author": "knotteye", "scripts": { "start": "node build/controller.js", - "build": "tsc" + "build": "tsc", + "user": "node build/cli.js" }, "repository": { "type": "git", @@ -15,12 +16,14 @@ "dependencies": { "bcrypt": "^3.0.6", "config": "^3.2.2", + "flags": "^0.1.3", "irc": "^0.5.2", "mysql": "^2.17.1", "node-media-server": ">=2.1.3 <3.0.0", "toml": "^3.0.0" }, "devDependencies": { - "@types/node": "^12.7.5" + "@types/node": "^12.7.5", + "typescript": "^3.6.3" } } diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..f23bc3e --- /dev/null +++ b/src/cli.ts @@ -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(); + }); +} \ No newline at end of file diff --git a/src/controller.ts b/src/controller.ts index b9bff10..712b62f 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -3,23 +3,7 @@ import * as ircd from "./ircd"; import * as db from "./database"; 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{ - //init(); const dbcfg = config.database; const bcryptcfg = config.bcrypt; const satyr: object = { diff --git a/src/database.ts b/src/database.ts index 504fc8c..e3c50ce 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,26 +1,67 @@ import * as mysql from "mysql"; import * as bcrypt from "bcrypt"; +import * as crypto from "crypto"; +import { resolve } from "url"; var raw: any; -var cryptoconfig: object; +var cryptoconfig: any; function run (db: object, bcrypt: object){ raw = mysql.createPool(db); 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){ - 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 }; \ No newline at end of file +export { query, raw, run, addUser, rmUser, addStreamKey, rmStreamKey }; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 3e20b8c..63f5e93 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,6 @@ import * as NodeMediaServer from "node-media-server"; import { mkdir } from "fs"; import * as db from "./database"; -//import { transcode } from "buffer"; why is this here? const { exec } = require('child_process'); function boot (mediaconfig: any, satyrconfig: any) { @@ -20,20 +19,20 @@ function boot (mediaconfig: any, satyrconfig: any) { return false; } 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') { + //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.`); } else{ console.log("[NodeMediaServer] Non-local Publish to public endpoint, rejecting stream:",id); session.reject(); + return false; } console.log("[NodeMediaServer] Public endpoint, checking record flag."); - //if this stream is from the public endpoint, stream - db.raw.query('select username from users where username=\''+key+'\' and record_flag=true limit 1', (error, results, fields) => { - if (error) {throw error;} + //if this stream is from the public endpoint, check if we should be recording + return db.query('select username from users where username=\''+key+'\' and record_flag=true limit 1').then((results) => { if(results[0].username && satyrconfig.record){ console.log('[NodeMediaServer] Initiating recording for stream:',id); 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 { console.log('[NodeMediaServer] Skipping recording for stream:',id); } + return true; }); - return true; } if(app !== satyrconfig.privateEndpoint){ //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 //grab the username from the database and redirect the stream there if the key is valid //otherwise kill the session - db.raw.query('select username from users where stream_key=\''+key+'\' limit 1', (error, results, fields) => { - if (error) {throw error;} + db.query('select username from users where stream_key=\''+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);