diff --git a/db_setup.sql b/db_setup.sql new file mode 100644 index 0000000..922287f --- /dev/null +++ b/db_setup.sql @@ -0,0 +1,11 @@ +CREATE USER 'satyr'@'localhost' IDENTIFIED BY 'password'; +CREATE DATABASE satyr_db; +GRANT ALL PRIVILEGES ON satyr_db.* TO 'satyr'@'localhost'; +USE satyr_db; +CREATE TABLE users( + username VARCHAR(25), + password_hash BINARY(60), + stream_key CHAR(20), + record_flag TINYINT, + is_mod TINYINT +); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0cc52d9..3a1bfc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,11 @@ "node-pre-gyp": "0.12.0" } }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -136,6 +141,11 @@ "supports-color": "^5.3.0" } }, + "check-localhost": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/check-localhost/-/check-localhost-0.0.1.tgz", + "integrity": "sha1-5EBBSdDrQr52SE/EnHTTIYOgC60=" + }, "chownr": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", @@ -579,6 +589,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "mysql": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.17.1.tgz", + "integrity": "sha512-7vMqHQ673SAk5C8fOzTG2LpPcf3bNt0oL3sFpxPEEFp1mdlDcrLK0On7z8ZYKaaHrHwNcQ/MTUz7/oobZ2OyyA==", + "requires": { + "bignumber.js": "7.2.1", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + } + }, "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", @@ -896,6 +917,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", diff --git a/package.json b/package.json index 3e40b22..5f19329 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ }, "dependencies": { "bcrypt": "^3.0.6", + "check-localhost": "0.0.1", "irc": "^0.5.2", + "mysql": "^2.17.1", "node-media-server": ">=2.1.3 <3.0.0" }, "devDependencies": { diff --git a/src/controller.ts b/src/controller.ts index 99c708e..1c03b08 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -1,31 +1,42 @@ import * as mediaserver from "./server"; import * as ircd from "./ircd"; +import * as db from "./database"; + +const mediaconfig: any = { + rtmp: { + port: 1935, + chunk_size: 60000, + gop_cache: true, + ping: 30, + ping_timeout: 60 + }, + http: { + port:8000, + allow_origin: '*', + mediaroot: './site' + }, + trans: { + ffmpeg: '/usr/bin/ffmpeg', + tasks: [ + { + app: 'live', + hls: 'true', + hlsFlags: '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]' + } + ] + } +}; + +const dbconfig: any = { + connectionLimit: 50, + host : 'localhost', + user : 'satyr', + password : 'password', + database : 'satyr_db' +}; function boot(): void{ - const mediaconfig: any = { - rtmp: { - port: 1935, - chunk_size: 60000, - gop_cache: true, - ping: 30, - ping_timeout: 60 - }, - http: { - port:8000, - allow_origin: '*', - mediaroot: './site' - }, - trans: { - ffmpeg: '/usr/bin/ffmpeg', - tasks: [ - { - app: 'live', - hls: 'true', - hlsFlags: '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]' - } - ] - } - }; + db.run(dbconfig); mediaserver.boot(mediaconfig); ircd.boot(); } diff --git a/src/database.ts b/src/database.ts new file mode 100644 index 0000000..8cf802b --- /dev/null +++ b/src/database.ts @@ -0,0 +1,24 @@ +import * as mysql from "mysql"; +import * as bcrypt from "bcrypt"; +var raw: any; + +function run (dbconfig: any){ + raw = mysql.createPool(dbconfig); +} + +function streamKeyAuth(key: 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 }; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index a167d36..e744cf9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,82 +1,92 @@ import * as NodeMediaServer from "node-media-server"; import { mkdir } from "fs"; +import * as db from "./database"; +const isLocal = require("check-localhost"); const { exec } = require('child_process'); -//initialize configs, eventually grab from runtime config file -function initConfig(): void{ - ; -} - -/*function streamAuth(path: string){ - if (path.split("/").length > 3){ - console.log("[NodeMediaServer] Malformed URL, closing connection."); - return false; - } - let app: string = path.split("/")[1]; - let key: string = path.split("/")[2]; - console.log("[NodeMediaServer] Authenticating stream with credentials: ",`app=${app} key=${key}`); - if (app !== "stream"){ - console.log("[NodeMediaServer] Invalid app name, closing connection."); - return false; - } - console.log("[NodeMediaServer] App name ok."); - if (key !== "temp"){ - console.log("[NodeMediaServer] Invalid stream key, closing connection."); - return false; - } - console.log("[NodeMediaServer] Stream key ok."); - return true; -}*/ - -function boot (config: any){ - const nms = new NodeMediaServer(config); - +function boot (mediaconfig: any) { + const nms = new NodeMediaServer(mediaconfig); nms.run(); - nms.on('prePublish', (id, StreamPath, args) => { - console.log("[NodeMediaServer] Prepublish Hook for stream id=",id); + nms.on('postPublish', (id, StreamPath, args) => { + console.log("[NodeMediaServer] Prepublish Hook for stream:",id); let session = nms.getSession(id); + let app: string = StreamPath.split("/")[1]; + let key: string = StreamPath.split("/")[2]; if (StreamPath.split("/").length > 3){ - console.log("[NodeMediaServer] Malformed URL, closing connection."); + console.log("[NodeMediaServer] Malformed URL, closing connection for stream:",id); session.reject(); return false; } - let app: string = StreamPath.split("/")[1]; - let key: string = StreamPath.split("/")[2]; - console.log("[NodeMediaServer] Authenticating stream with credentials: ",`app=${app} key=${key}`); - if (app !== "stream"){ - console.log("[NodeMediaServer] Invalid app name, closing connection."); + if(app === "live") { + isLocal(session.ip).then( (local) => { + if(local) { + console.log("[NodeMediaServer] Local publish, stream:",`${id} ok.`); + } + else{ + console.log("[NodeMediaServer] Non-local Publish to /live, rejecting stream:",id); + session.reject(); + } + }); + console.log("[NodeMediaServer] Public endpoint, checking record flag."); + db.raw.query('select username,record_flag from users where username=\''+key+'\' and record_flag=true limit 1', (error, results, fields) => { + if (error) {throw error;} + if(results[0]){ + console.log('[NodeMediaServer] Initiating recording for stream:',id); + mkdir('./site/live/'+results[0].username, { recursive : true }, (err) => { + if (err) throw err; + }); + let subprocess = exec('ffmpeg -i rtmp://127.0.0.1/live/'+results[0].username+' -vcodec copy -acodec copy ./site/live/'+results[0].username+'/$(date +%d%b%Y-%H%M).mp4',{ + detached : true, + stdio : 'inherit' + }); + subprocess.unref(); + //spawn an ffmpeg process to record the stream, then detach it completely + } + else { + console.log('[NodeMediaServer] Skipping recording for stream:',id); + } + }); + return true; + } + if(app !== "stream"){ + //app isn't 'live' if we've reached this point + console.log("[NodeMediaServer] Wrong endpoint, rejecting stream:",id); session.reject(); return false; } - console.log("[NodeMediaServer] App name ok."); - //TODO: Hook up to DB and redirect from query - if (key !== "temp"){ - console.log("[NodeMediaServer] Invalid stream key, closing connection."); + db.raw.query('select username from users where stream_key=\''+key+'\' limit 1', (error, results, fields) => { + if (error) {throw error;} + if(results[0]){ + exec('ffmpeg -analyzeduration 0 -i rtmp://localhost/stream/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://localhost:1935/live/'+results[0].username); + console.log('[NodeMediaServer] Stream key okay for stream:',id); + } + else{ + console.log('[NodeMediaServer] Invalid stream key for stream:',id); + session.reject(); + } + }); + }); + nms.on('prePlay', (id, StreamPath, args) => { + let session = nms.getSession(id); + let app: string = StreamPath.split("/")[1]; + let key: string = StreamPath.split("/")[2]; + if (StreamPath.split("/").length > 3){ + console.log("[NodeMediaServer] Malformed URL, closing connection for stream:",id); session.reject(); return false; } - console.log("[NodeMediaServer] Stream key ok."); - session.publishStreamPath = "/live/amy"; - }); - - nms.on('postPublish', (id, StreamPath, args) => { - console.log('[NodeMediaServer] Checking record flag for ', `id=${id} StreamPath=${StreamPath}`); - //Hook up to postgres DB. - if(true){ - console.log('[NodeMediaServer] Initiating recording for ', `id=${id} StreamPath=${StreamPath}`); - mkdir('./media'+StreamPath, { recursive : true }, (err) => { - if (err) throw err; + if(app === "stream") { + isLocal(session.ip).then( (local) => { + if(local) { + console.log("[NodeMediaServer] Local play, client:",`${id} ok.`); + } + else{ + console.log("[NodeMediaServer] Non-local Play from /stream, rejecting client:",id); + session.reject(); + } }); - let subprocess = exec('ffmpeg -i rtmp://127.0.0.1'+StreamPath+' -vcodec copy -acodec copy ./media'+StreamPath+'/$(date +%d%b%Y-%H%M).mp4',{ - detached : true, - stdio : 'inherit' - }); - subprocess.unref(); - //spawn an ffmpeg process to record the stream, then detach it completely - return true; } - console.log('[NodeMediaServer] Skipping recording for ', `id=${id} StreamPath=${StreamPath}`); }); } export { boot }; \ No newline at end of file