var express = require('express'); var fs = require('fs'); var bodyParser = require('body-parser'); var os = require('os'); var moment = require('moment'); var fetch = require('node-fetch'); var execSync = require('child_process').execSync; var exec = require('child_process').exec; var spawn = require('child_process').spawn; var httpProxy = require('http-proxy'); var cors = require('cors') var proxy = httpProxy.createProxyServer({}) var ejs = require('ejs'); var fileupload = require("express-fileupload"); var fieldBuild = require('./fieldBuild'); module.exports = function(s,config,lang,app,io){ const { ptzControl, setPresetForCurrentPosition } = require('./control/ptz.js')(s,config,lang,app,io) const { triggerEvent, } = require('./events/utils.js')(s,config,lang) const { basicAuth, superLogin, createTwoFactorAuth, twoFactorVerification, ldapLogin, } = require('./auth/utils.js')(s,config,lang) const { spawnSubstreamProcess, destroySubstreamProcess, removeSenstiveInfoFromMonitorConfig, sendSubstreamEvent, } = require('./monitor/utils.js')(s,config,lang) const { sliceVideo, archiveVideo, reEncodeVideoAndReplace, reEncodeVideoAndBinOriginalAddToQueue, getVideosBasedOnTagFoundInMatrixOfAssociatedEvent, mergeVideosAndBin, } = require('./video/utils.js')(s,config,lang) s.renderPage = function(req,res,paths,passables,callback){ passables.window = {} passables.data = req.params passables.originalURL = s.getOriginalUrl(req) passables.baseUrl = req.protocol+'://'+req.hostname passables.config = s.getConfigWithBranding(req.hostname) passables.fieldBuild = fieldBuild res.render(paths,passables,callback) } //child node proxy check //params = parameters //cb = callback //res = response, only needed for express (http server) //request = request, only needed for express (http server) s.checkChildProxy = function(params,cb,res,req) { if(s.group[params.ke] && s.group[params.ke].activeMonitors && s.group[params.ke].activeMonitors[params.id] && s.group[params.ke].activeMonitors[params.id].childNode){ var url = 'http://' + s.group[params.ke].activeMonitors[params.id].childNode// + req.originalUrl proxy.web(req, res, { target: url }) }else{ cb() } } s.closeJsonResponse = function(res,endData){ res.setHeader('Content-Type', 'application/json') res.end(s.prettyPrint(endData)) } //get post data s.getPostData = function(req,target,parseJSON){ if(!target)target = 'data' if(!parseJSON)parseJSON = true var postData = false if(req.query && req.query[target]){ postData = req.query[target] }else{ postData = req.body[target] } if(parseJSON === true){ postData = s.parseJSON(postData) } return postData } //get client ip address s.getClientIp = function(req){ return req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress; } ////Pages app.enable('trust proxy'); if(config.webPaths.home !== '/'){ app.use('/libs',express.static(s.mainDirectory + '/web/libs')) } [ [config.webPaths.home,'libs','/web/libs'], [config.webPaths.super,'libs','/web/libs'], [config.webPaths.home,'assets','/web/assets'], [config.webPaths.super,'assets','/web/assets'], ].forEach((piece) => { app.use(s.checkCorrectPathEnding(piece[0])+piece[1],express.static(s.mainDirectory + piece[2])) }) app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(cors()); app.set('views', s.mainDirectory + '/web'); app.set('view engine','ejs'); //add template handler if(config.renderPaths.handler!==undefined){require(s.mainDirectory+'/web/'+config.renderPaths.handler+'.js').addHandlers(s,app,io,config)} /** * API : Logout */ app.get(config.webPaths.apiPrefix+':auth/logout/:ke/:id', function (req,res){ const groupKey = req.params.ke const userId = req.params.id const authToken = req.params.auth const user = s.group[groupKey] && s.group[groupKey].users[authToken] && s.group[groupKey].users[authToken].details ? s.group[groupKey].users[authToken] : null; if(user){ const clientIp = s.getClientIp(req) const user = s.group[groupKey].users[authToken] s.onLogoutExtensions.forEach((extender) => { extender(user,groupKey,userId,clientIp) }); delete(s.api[authToken]); delete(s.group[groupKey].users[authToken]); s.knexQuery({ action: "update", table: "Users", update: { auth: '', }, where: [ ['auth','=',authToken], ['ke','=',groupKey], ['uid','=',userId], ] }) res.end(s.prettyPrint({ok:true,msg:'You have been logged out, session key is now inactive.'})) }else{ res.end(s.prettyPrint({ok:false,msg:'This group key does not exist or this user is not logged in.'})) } }); /** * Page : Login Screen */ app.get(config.webPaths.home, function (req,res){ s.renderPage(req,res,config.renderPaths.index,{lang:lang,config: s.getConfigWithBranding(req.hostname),screen:'dashboard'}) }); /** * Page : Superuser Login Screen */ app.get(config.webPaths.super, function (req,res){ s.renderPage(req,res,config.renderPaths.index,{ lang: lang, config: s.getConfigWithBranding(req.hostname), screen: 'super' }) }); /** * API : Get User Info */ app.get(config.webPaths.apiPrefix+':auth/userInfo/:ke',function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ response.ok = true response.user = user res.end(s.prettyPrint(response)); },res,req); }) //login function s.deleteFactorAuth=function(r){ delete(s.factorAuth[r.ke][r.uid]) if(Object.keys(s.factorAuth[r.ke]).length===0){ delete(s.factorAuth[r.ke]) } } /** * Page : Admin page redirect to regular page now */ app.get(config.webPaths.admin, function (req,res){ res.redirect('/'); }); /** * API : Login handler. Dashboard, Streamer, Dashcam Administrator, Superuser */ app.post([ config.webPaths.home, config.webPaths.super, s.checkCorrectPathEnding(config.webPaths.home)+':screen', s.checkCorrectPathEnding(config.webPaths.super)+':screen', ],async function (req,res){ var response = {ok: false}; req.ip = s.getClientIp(req) const screenChooser = function(screen){ var search = function(screen){ if(req.url.indexOf(screen) > -1){ return true } return false } switch(true){ // case search(config.webPaths.admin): // return 'admin' // break; case search(config.webPaths.super): return 'super' break; default: return 'dashboard' break; } } // brute check if(s.failedLoginAttempts[req.body.mail] && s.failedLoginAttempts[req.body.mail].failCount >= 5){ if(req.query.json=='true'){ res.end(s.prettyPrint({ok:false})) }else{ s.renderPage(req,res,config.renderPaths.index,{ failedLogin: true, message: lang.failedLoginText1, lang: s.copySystemDefaultLanguage(), config: s.getConfigWithBranding(req.hostname), screen: screenChooser(req.params.screen) }) } return false } // const renderPage = function(focus,data){ if(s.failedLoginAttempts[req.body.mail]){ clearTimeout(s.failedLoginAttempts[req.body.mail].timeout) delete(s.failedLoginAttempts[req.body.mail]) } if(req.query.json=='true'){ delete(data.config); delete(data.__dirname); delete(data.customAutoLoad); delete(data.fs); data.timezone = config.timezone data.ok = true; res.setHeader('Content-Type', 'application/json'); res.end(s.prettyPrint(data)) }else{ data.screen=req.params.screen s.renderPage(req,res,focus,data) } } const failedAuthentication = function(board,failIdentifier,failMessage){ // brute protector if(!failIdentifier){ s.renderPage(req,res,config.renderPaths.index,{ failedLogin: true, message: failMessage || lang.failedLoginText2, lang: s.copySystemDefaultLanguage(), config: s.getConfigWithBranding(req.hostname), screen: screenChooser(req.params.screen) }) return } if(!s.failedLoginAttempts[failIdentifier]){ s.failedLoginAttempts[failIdentifier] = { failCount : 0, ips : {} } } ++s.failedLoginAttempts[failIdentifier].failCount if(!s.failedLoginAttempts[failIdentifier].ips[req.ip]){ s.failedLoginAttempts[failIdentifier].ips[req.ip] = 0 } ++s.failedLoginAttempts[failIdentifier].ips[req.ip] clearTimeout(s.failedLoginAttempts[failIdentifier].timeout) s.failedLoginAttempts[failIdentifier].timeout = setTimeout(function(){ delete(s.failedLoginAttempts[failIdentifier]) },1000 * 60 * 15) // check if JSON if(req.query.json === 'true'){ res.setHeader('Content-Type', 'application/json') res.end(s.prettyPrint({ok:false})) }else{ s.renderPage(req,res,config.renderPaths.index,{ failedLogin: true, message: failMessage || lang.failedLoginText2, lang: s.copySystemDefaultLanguage(), config: s.getConfigWithBranding(req.hostname), screen: screenChooser(req.params.screen) }) } var logTo = { ke: '$', mid: '$USER' } var logData = { type: lang['Authentication Failed'], msg: { for: board, mail: failIdentifier, ip: req.ip } } if(board === 'super'){ s.userLog(logTo,logData) }else{ s.knexQuery({ action: "select", columns: "ke,uid,details", table: "Users", where: [ ['mail','=',failIdentifier], ] },(err,r) => { if(r && r[0]){ r = r[0] r.details = JSON.parse(r.details) r.lang = s.getLanguageFile(r.details.lang) logData.id = r.uid logData.type = r.lang['Authentication Failed'] logTo.ke = r.ke } s.userLog(logTo,logData) }) } } function checkRoute(pageTarget,userInfo){ if(!userInfo.lang){ userInfo.lang = s.getLanguageFile(userInfo.details.lang) } switch(pageTarget){ case'cam': renderPage(config.renderPaths.dashcam,{ // config: s.getConfigWithBranding(req.hostname), $user: userInfo, lang: userInfo.lang, define: s.getDefinitonFile(userInfo.details.lang), __dirname: s.mainDirectory, customAutoLoad: s.customAutoLoadTree }) break; case'streamer': renderPage(config.renderPaths.streamer,{ // config: s.getConfigWithBranding(req.hostname), $user: userInfo, lang: userInfo.lang, define: s.getDefinitonFile(userInfo.details.lang), __dirname: s.mainDirectory, customAutoLoad: s.customAutoLoadTree }) break; case'admin': // dash default: var chosenRender = 'home' if(userInfo.details.sub && userInfo.details.landing_page && userInfo.details.landing_page !== '' && config.renderPaths[userInfo.details.landing_page]){ chosenRender = userInfo.details.landing_page } renderPage(config.renderPaths[chosenRender],{ $user: userInfo, config: s.getConfigWithBranding(req.hostname), lang: userInfo.lang, define: s.getDefinitonFile(userInfo.details.lang), addStorage: s.dir.addStorage, fs: fs, __dirname: s.mainDirectory, customAutoLoad: s.customAutoLoadTree }) break; } s.userLog({ ke: userInfo.ke, mid: '$USER' },{ type: userInfo.lang['New Authentication Token'], msg: { for: pageTarget, mail: userInfo.mail, id: userInfo.uid, ip: req.ip } }) } if(req.body.alternateLogin && s.alternateLogins[req.body.alternateLogin]){ const alternateLogin = s.alternateLogins[req.body.alternateLogin] const alternateLoginResponse = await alternateLogin(req.body) if(alternateLoginResponse.ok && alternateLoginResponse.user){ const user = alternateLoginResponse.user const sessionKey = s.md5(s.gid()) user.auth = sessionKey s.knexQuery({ action: "update", table: "Users", update: { auth: sessionKey }, where: [ ['ke','=',user.ke], ['uid','=',user.uid], ] }) checkRoute(req.body.function,{ ok: true, auth_token: user.auth, ke: user.ke, uid: user.uid, mail: user.mail, details: user.details }) }else{ return failedAuthentication(req.body.function,req.body.mail,alternateLoginResponse.msg) } }else if(req.body.mail && req.body.pass){ async function regularLogin(){ const basicAuthResponse = await basicAuth(req.body.mail,req.body.pass) if(basicAuthResponse.user){ const user = basicAuthResponse.user; const sessionKey = s.md5(s.gid()) user.auth = sessionKey user.lang = s.getLanguageFile(user.details.lang) s.knexQuery({ action: "update", table: "Users", update: { auth: user.auth }, where: [ ['ke','=',user.ke], ['uid','=',user.uid], ] }) if(user.details.factorAuth === "1"){ const factorAuthCreationResponse = createTwoFactorAuth( user, req.body.machineID || s.md5(s.gid()), req.body.function ); if(!factorAuthCreationResponse.goToDashboard){ renderPage(config.renderPaths.factorAuth,{ $user:{ ke: user.ke, uid: user.uid, mail: user.mail, details: { sub: user.details.sub } }, lang: user.lang, }) return; } } checkRoute(req.body.function,{ ok: true, auth_token: user.auth, ke: user.ke, uid: user.uid, mail: user.mail, details: user.details }) }else{ failedAuthentication(req.body.function,req.body.mail) } } if(req.body.function === 'super' && !config.superUserLoginDisabled){ const superLoginResponse = await superLogin(req.body.mail,req.body.pass); if(superLoginResponse.ok){ renderPage(config.renderPaths.super,{ config: config, lang: lang, define: s.getDefinitonFile(config.language), $user: superLoginResponse.user, customAutoLoad: s.customAutoLoadTree, currentVersion: s.currentVersion, }) }else{ failedAuthentication(req.body.function,req.body.mail) } }else{ regularLogin() } }else if( req.body.machineID && req.body.factorAuthKey && s.factorAuth[req.body.ke] && s.factorAuth[req.body.ke][req.body.id] ){ const factorAuthObject = s.factorAuth[req.body.ke][req.body.id] const twoFactorVerificationResponse = twoFactorVerification({ ke: req.body.ke, id: req.body.id, machineID: req.body.machineID, factorAuthKey: req.body.factorAuthKey, }) if(twoFactorVerificationResponse.ok){ checkRoute(twoFactorVerificationResponse.pageTarget,twoFactorVerificationResponse.info) }else{ failedAuthentication(lang['2-Factor Authentication'],factorAuthObject.info.mail) } }else{ failedAuthentication(lang['2-Factor Authentication'],req.body.mail) } }) /** * API : Brute Protection Lock Reset by API */ app.get([config.webPaths.apiPrefix+':auth/resetBruteProtection/:ke'], function (req,res){ s.auth(req.params,function(user){ if(s.failedLoginAttempts[user.mail]){ clearTimeout(s.failedLoginAttempts[user.mail].timeout) delete(s.failedLoginAttempts[user.mail]) } res.end(s.prettyPrint({ok:true})) }) }) // /** // * Page : Montage - stand alone squished view with gridstackjs // */ // app.get([ // config.webPaths.apiPrefix+':auth/grid/:ke', // config.webPaths.apiPrefix+':auth/grid/:ke/:group', // config.webPaths.apiPrefix+':auth/cycle/:ke', // config.webPaths.apiPrefix+':auth/cycle/:ke/:group' // ], function(req,res) { // s.auth(req.params,function(user){ // if(user.permissions.get_monitors==="0"){ // res.end(user.lang['Not Permitted']) // return // } // // req.params.protocol=req.protocol; // req.sql='SELECT * FROM Monitors WHERE mode!=? AND mode!=? AND ke=?';req.ar=['stop','idle',req.params.ke]; // if(!req.params.id){ // if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){ // try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){} // req.or=[]; // user.details.monitors.forEach(function(v,n){ // req.or.push('mid=?');req.ar.push(v) // }) // req.sql+=' AND ('+req.or.join(' OR ')+')' // } // }else{ // if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){ // req.sql+=' and mid=?';req.ar.push(req.params.id) // }else{ // res.end(user.lang['There are no monitors that you can view with this account.']); // return; // } // } // s.sqlQuery(req.sql,req.ar,function(err,r){ // if(req.params.group){ // var filteredByGroupCheck = {}; // var filteredByGroup = []; // r.forEach(function(v,n){ // var details = JSON.parse(r[n].details); // try{ // req.params.group.split('|').forEach(function(group){ // var groups = JSON.parse(details.groups); // if(groups.indexOf(group) > -1 && !filteredByGroupCheck[v.mid]){ // filteredByGroupCheck[v.mid] = true; // filteredByGroup.push(v) // } // }) // }catch(err){ // // } // }) // r = filteredByGroup; // } // r.forEach(function(v,n){ // if(s.group[v.ke]&&s.group[v.ke].activeMonitors[v.mid]&&s.group[v.ke].activeMonitors[v.mid].watch){ // r[n].currentlyWatching=Object.keys(s.group[v.ke].activeMonitors[v.mid].watch).length // } // r[n].subStream={} // var details = JSON.parse(r[n].details) // if(details.snap==='1'){ // r[n].subStream.jpeg = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg' // } // if(details.stream_channels&&details.stream_channels!==''){ // try{ // details.stream_channels=JSON.parse(details.stream_channels) // r[n].channels=[] // details.stream_channels.forEach(function(b,m){ // var streamURL // switch(b.stream_type){ // case'mjpeg': // streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+'/'+m // break; // case'hls': // streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+'/'+m+'/s.m3u8' // break; // case'h264': // streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+'/'+m // break; // case'flv': // streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+'/'+m+'/s.flv' // break; // case'mp4': // streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+'/'+m+'/s.mp4' // break; // } // r[n].channels.push(streamURL) // }) // }catch(err){ // s.userLog(req.params,{type:'Broken Monitor Object',msg:'Stream Channels Field is damaged. Skipping.'}) // } // } // }) // var page = config.renderPaths.grid // if(req.path.indexOf('/cycle/') > -1){ // page = config.renderPaths.cycle // } // s.renderPage(req,res,page,{ // data:Object.assign(req.params,req.query), // baseUrl:req.protocol+'://'+req.hostname, // config: s.getConfigWithBranding(req.hostname), // lang:user.lang, // $user:user, // monitors:r, // query:req.query // }); // }) // },res,req) // }); /** * API : Get TV Channels (Monitor Streams) json */ app.get([config.webPaths.apiPrefix+':auth/tvChannels/:ke',config.webPaths.apiPrefix+':auth/tvChannels/:ke/:id','/get.php'], function (req,res){ var response = {ok:false}; if(req.query.username&&req.query.password){ req.params.username = req.query.username req.params.password = req.query.password } var output = ['h264','hls','mp4'] if( req.query.output && req.query.output !== '' ){ output = req.query.output.split(',') output.forEach(function(type,n){ if(type === 'ts'){ output[n] = 'h264' if(output.indexOf('hls') === -1){ output.push('hls') } } }) } const isM3u8 = req.query.type === 'm3u8' || req.query.type === 'm3u_plus' s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_monitors`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,[]); return } s.knexQuery({ action: "select", columns: "*", table: "Monitors", where: [ ['ke','=',groupKey], ['mode','!=','stop'], monitorRestrictions ] },(err,r) => { var tvChannelMonitors = []; r.forEach(function(v,n){ var buildStreamURL = function(channelRow,type,channelNumber){ var streamURL if(req.query.streamtype && req.query.streamtype != type){ return } if(channelNumber){channelNumber = '/' + channelNumber}else{channelNumber = ''} switch(type){ case'mjpeg': streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber break; case'hls': streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8' break; case'h264': streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber break; case'flv': streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv' break; case'mp4': streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.ts' break; } if(streamURL){ if(!channelRow.streamsSortedByType[type]){ channelRow.streamsSortedByType[type]=[] } channelRow.streamsSortedByType[type].push(streamURL) channelRow.streams.push(streamURL) } return streamURL } var details = JSON.parse(r[n].details); if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5) var channelRow = { ke:v.ke, mid:v.mid, type:v.type, groupTitle:details.tv_channel_group_title, channel:details.tv_channel_id, }; if(details.snap==='1'){ channelRow.snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg' } channelRow.streams=[] channelRow.streamsSortedByType={} buildStreamURL(channelRow,details.stream_type) if(details.stream_channels&&details.stream_channels!==''){ details.stream_channels=JSON.parse(details.stream_channels) details.stream_channels.forEach(function(b,m){ buildStreamURL(channelRow,b.stream_type,m.toString()) }) } if(details.tv_channel==='1'){ tvChannelMonitors.push(channelRow) } }) if(isM3u8){ var m3u8 = '#EXTM3U'+'\n' tvChannelMonitors.forEach(function(channelRow,n){ output.forEach(function(type){ if(channelRow.streamsSortedByType[type]){ if(req.query.type === 'm3u_plus'){ m3u8 +='#EXTINF-1 tvg-id="'+channelRow.mid+'" tvg-name="'+channelRow.channel+'" tvg-logo="'+req.protocol+'://'+req.headers.host+channelRow.snapshot+'" group-title="'+channelRow.groupTitle+'",'+channelRow.channel+'\n' }else{ m3u8 +='#EXTINF:-1,'+channelRow.channel+' ('+type.toUpperCase()+') \n' } m3u8 += req.protocol+'://'+req.headers.host+channelRow.streamsSortedByType[type][0]+'\n' } }) }) res.end(m3u8) }else{ if(tvChannelMonitors.length === 1)tvChannelMonitors=tvChannelMonitors[0]; s.closeJsonResponse(res,tvChannelMonitors) } }) },res,req); }); /** * API : Get Monitors */ app.get([config.webPaths.apiPrefix+':auth/monitor/:ke',config.webPaths.apiPrefix+':auth/monitor/:ke/:id'], function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,(user) => { const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, userPermissions, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_monitors`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,[]); return } const cannotSeeImportantSettings = (isRestrictedApiKey && apiKeyPermissions.edit_monitors_disallowed) || userPermissions.monitor_create_disallowed; const whereQuery = [ ['ke','=',groupKey], monitorRestrictions ]; if(!!req.query.search){ const searchQuery = req.query.search.split(','); const whereQuerySearch = [] for(item of searchQuery){ if(item){ whereQuerySearch.push( whereQuerySearch.length === 0 ? ['name','LIKE',`%${item.trim()}%`] : ['or', 'name','LIKE',`%${item}%`], ['or','mid','LIKE',`%${item.trim()}%`] ); } } whereQuery.push(whereQuerySearch) } s.knexQuery({ action: "select", columns: "*", table: "Monitors", where: whereQuery },(err,r) => { r.forEach(function(v,n){ const monitorId = v.mid; v.details = JSON.parse(v.details) var details = v.details; if(isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`] || cannotSeeImportantSettings){ r[n] = removeSenstiveInfoFromMonitorConfig(v); } if(s.group[v.ke] && s.group[v.ke].activeMonitors[v.mid]){ const activeMonitor = s.group[v.ke].activeMonitors[v.mid] r[n].currentlyWatching = Object.keys(activeMonitor.watch).length r[n].currentCpuUsage = activeMonitor.currentCpuUsage r[n].status = activeMonitor.monitorStatus r[n].code = activeMonitor.monitorStatusCode r[n].subStreamChannel = activeMonitor.subStreamChannel r[n].subStreamActive = !!activeMonitor.subStreamProcess } function getStreamUrl(type,channelNumber){ var streamURL if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''} switch(type){ case'mjpeg': streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber break; case'hls': streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8' break; case'h264': streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber break; case'flv': streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv' break; case'mp4': streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4' break; case'useSubstream': try{ const monitorConfig = s.group[v.ke].rawMonitorConfigurations[v.mid] const monitorDetails = monitorConfig.details const subStreamChannelNumber = 1 + (monitorDetails.stream_channels || []).length const subStreamType = monitorConfig.details.substream.output.stream_type streamURL = getStreamUrl(subStreamType,subStreamChannelNumber) }catch(err){ s.debugLog(err) } break; } return streamURL } var buildStreamURL = function(type,channelNumber){ var streamURL = getStreamUrl(type,channelNumber) if(streamURL){ if(!r[n].streamsSortedByType[type]){ r[n].streamsSortedByType[type]=[] } r[n].streamsSortedByType[type].push(streamURL) r[n].streams.push(streamURL) } return streamURL } if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5) if(details.snap==='1'){ r[n].snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg' } r[n].streams=[] r[n].streamsSortedByType={} buildStreamURL(details.stream_type) if(details.stream_channels&&details.stream_channels!==''){ details.stream_channels=s.parseJSON(details.stream_channels) details.stream_channels.forEach(function(b,m){ buildStreamURL(b.stream_type,m.toString()) }) } }) s.closeJsonResponse(res,r); }) },res,req); }); /** * API : Toggle Substream Process on and off */ app.get(config.webPaths.apiPrefix+':auth/toggleSubstream/:ke/:id', function (req,res){ const response = {ok: false}; s.auth(req.params,async (user) => { const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, userPermissions, apiKeyPermissions, isRestrictedApiKey, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed || isRestricted && !monitorPermissions[`${monitorId}_monitors`] ){ response.msg = user.lang['Not Permitted'] }else{ const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] const activeMonitor = s.group[groupKey].activeMonitors[monitorId] const substreamConfig = monitorConfig.details.substream const theAction = req.query.action if( substreamConfig.output ){ switch(theAction){ case'status': response.ok = true response.isRunning = !!activeMonitor.subStreamProcess; response.channel = activeMonitor.subStreamChannel; break; case'stop': activeMonitor.allowDestroySubstream = true await destroySubstreamProcess(activeMonitor) break; default: if(!activeMonitor.subStreamProcess){ response.ok = true activeMonitor.allowDestroySubstream = false; spawnSubstreamProcess(monitorConfig) response.channel = activeMonitor.subStreamChannel; }else{ sendSubstreamEvent(groupKey, monitorId) } break; } // if(!activeMonitor.subStreamProcess){ // response.ok = true // activeMonitor.allowDestroySubstream = false; // spawnSubstreamProcess(monitorConfig) // }else{ // activeMonitor.allowDestroySubstream = true // await destroySubstreamProcess(activeMonitor) // } }else{ response.msg = lang['Invalid Settings'] } } s.closeJsonResponse(res,response); },res,req); }); /** * API : Get Active Event-Based Recording Info */ app.get(config.webPaths.apiPrefix+':auth/eventRecordings/:ke/:id', function (req,res){ const response = {ok: false}; s.auth(req.params,async (user) => { const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, userPermissions, apiKeyPermissions, isRestrictedApiKey, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed || isRestricted && !monitorPermissions[`${monitorId}_monitors`] ){ response.msg = user.lang['Not Permitted'] }else{ const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] const activeMonitor = s.group[groupKey].activeMonitors[monitorId] const fileTimes = Object.keys(activeMonitor.eventBasedRecording) response.fileTimes = fileTimes; response.ok = true; } s.closeJsonResponse(res,response); },res,req); }); // /** // * API : Merge Recorded Videos into one file // */ // app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){ // var failed = function(resp){ // res.setHeader('Content-Type', 'application/json'); // res.end(s.prettyPrint(resp)) // } // if(req.query.videos && req.query.videos !== ''){ // s.auth(req.params,function(user){ // var videosSelected = JSON.parse(req.query.videos) // const whereQuery = [] // var didOne = false // videosSelected.forEach(function(video){ // var time = s.nameToTime(video.filename) // if(req.query.isUTC === 'true'){ // time = s.utcToLocal(time) // } // if(didOne){ // whereQuery.push(['or','ke','=',req.params.ke]) // }else{ // didOne = true // whereQuery.push(['ke','=',req.params.ke]) // } // whereQuery.push( // ['mid','=',video.mid], // ['time','=',time], // ) // // }) // s.knexQuery({ // action: "select", // columns: "*", // table: "Videos", // where: whereQuery // },(err,r) => { // var resp = {ok: false} // if(r && r[0]){ // s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){ // res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"') // var file = fs.createReadStream(fullPath) // file.on('close',function(){ // setTimeout(function(){ // s.file('delete',fullPath) // },1000 * 60 * 3) // res.end() // }) // file.pipe(res) // }) // }else{ // failed({ok:false,msg:'No Videos Found'}) // } // }) // },res,req); // }else{ // failed({ok:false,msg:'"videos" query variable is missing from request.'}) // } // }) /** * API : Get Videos */ app.get([ config.webPaths.apiPrefix+':auth/videos/:ke', config.webPaths.apiPrefix+':auth/videos/:ke/:id', config.webPaths.apiPrefix+':auth/cloudVideos/:ke', config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id' ], function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId,'video_view') const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []}); return } var origURL = req.originalUrl.split('/') var videoParam = origURL[origURL.indexOf(req.params.auth) + 1] var videoSet = 'Videos' switch(videoParam){ case'cloudVideos': videoSet = 'Cloud Videos' break; } s.sqlQueryBetweenTimesWithPermissions({ table: videoSet, user: user, noCount: true, groupKey: req.params.ke, monitorId: req.params.id, startTime: req.query.start, endTime: req.query.end, startTimeOperator: req.query.startOperator, endTimeOperator: req.query.endOperator, noLimit: req.query.noLimit, limit: req.query.limit, archived: req.query.archived, endIsStartTo: !!req.query.endIsStartTo, parseRowDetails: false, rowName: 'videos', monitorRestrictions: monitorRestrictions, preliminaryValidationFailed: false },(response) => { if(response && response.videos){ s.buildVideoLinks(response.videos,{ auth : req.params.auth, videoParam : videoParam, hideRemote : config.hideCloudSaveUrls, }) } res.end(s.prettyPrint(response)) }) },res,req); }); /** * API : Get Videos */ app.get([ config.webPaths.apiPrefix+':auth/videosByEventTag/:ke', config.webPaths.apiPrefix+':auth/videosByEventTag/:ke/:id' ], function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const searchQuery = s.getPostData(req,'search') const startTime = s.getPostData(req,'start') const endTime = s.getPostData(req,'end') const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []}); return } getVideosBasedOnTagFoundInMatrixOfAssociatedEvent({ groupKey, monitorId, startTime, endTime, searchQuery, monitorRestrictions, }).then((response) => { if(response && response.rows){ s.buildVideoLinks(response.rows,{ auth : req.params.auth, videoParam : 'videos', }) } s.closeJsonResponse(res,{ ok: true, videos: response.rows, }) }) },res,req); }); /** * Page : Get Wall Video View (Wall Timeline) */ app.get(config.webPaths.apiPrefix+':auth/wallvideoview/:ke', function (req,res){ s.auth(req.params,function(user){ const authKey = req.params.auth const groupKey = req.params.ke if( user.permissions.watch_videos === "0" && user.details.allmonitors !== '1' ){ res.end(user.lang['Not Permitted']) return } s.renderPage(req,res,config.renderPaths.wallvideoview,{ forceUrlPrefix: req.query.host || '', data: req.params, protocol: req.protocol, baseUrl: req.protocol + '://' + req.hostname, config: s.getConfigWithBranding(req.hostname), define: s.getDefinitonFile(user.details ? user.details.lang : config.lang), lang: lang, $user: user, authKey: authKey, groupKey: groupKey, originalURL: s.getOriginalUrl(req) }); },res,req); }); /** * API : Get Events */ app.get([ config.webPaths.apiPrefix+':auth/events/:ke', config.webPaths.apiPrefix+':auth/events/:ke/:id' ], function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], events: []}); return } if(req.query.onlyCount === '1'){ const response = {ok: true} s.knexQuery({ action: "count", columns: "mid", table: "Events", where: [ ['ke','=',groupKey], ['time','>=',req.query.start], ['time','<=',req.query.end], monitorRestrictions ] },(err,r) => { if(err){ s.debugLog(err) response.ok = false }else{ response.count = r[0]['count(`mid`)'] } s.closeJsonResponse(res,response) }) }else{ s.sqlQueryBetweenTimesWithPermissions({ table: 'Events', user: user, groupKey: req.params.ke, monitorId: req.params.id, startTime: req.query.start, endTime: req.query.end, startTimeOperator: req.query.startOperator, endTimeOperator: req.query.endOperator, noLimit: req.query.noLimit, limit: req.query.limit, archived: req.query.archived, endIsStartTo: true, parseRowDetails: true, noFormat: true, noCount: true, rowName: 'events', preliminaryValidationFailed: false },(response) => { res.end(s.prettyPrint(response)) }) } }) }) /** * API : Get Logs */ app.get([ config.webPaths.apiPrefix+':auth/logs/:ke', config.webPaths.apiPrefix+':auth/logs/:ke/:id' ], function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { isRestricted, isRestrictedApiKey, apiKeyPermissions, userPermissions, } = s.checkPermission(user) if( userPermissions.view_logs_disallowed || isRestrictedApiKey && apiKeyPermissions.get_logs_disallowed ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], logs: []}); return } s.sqlQueryBetweenTimesWithPermissions({ table: 'Logs', user: user, groupKey: req.params.ke, monitorId: req.params.id, startTime: req.query.start, endTime: req.query.end, startTimeOperator: req.query.startOperator, endTimeOperator: req.query.endOperator, limit: req.query.limit || 50, endIsStartTo: true, noFormat: true, noCount: true, rowName: 'logs', preliminaryValidationFailed: false },(response) => { response.forEach(function(v,n){ v.info = JSON.parse(v.info) }) res.end(s.prettyPrint(response)) }) },res,req) }) /** * API : Monitor Mode Controller */ app.get([config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, userPermissions, } = s.checkPermission(user) if( userPermissions.monitor_create_disallowed || isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed || isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`] ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); return } if(req.params.f===''){response.msg = user.lang.monitorGetText1;res.end(s.prettyPrint(response));return} if(req.params.f!=='stop'&&req.params.f!=='start'&&req.params.f!=='record'){ response.msg = 'Mode not recognized.'; res.end(s.prettyPrint(response)); return; } s.knexQuery({ action: "select", columns: "*", table: "Monitors", where: [ ['ke','=',req.params.ke], ['mid','=',req.params.id], ], limit: 1 },async (err,r) => { if(r && r[0]){ r = r[0]; if(req.query.reset==='1'||(s.group[r.ke]&&s.group[r.ke].rawMonitorConfigurations[r.mid].mode!==req.params.f)||req.query.fps&&(!s.group[r.ke].activeMonitors[r.mid].currentState||!s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on)){ if(req.query.reset!=='1'||!s.group[r.ke].activeMonitors[r.mid].trigger_timer){ if(!s.group[r.ke].activeMonitors[r.mid].currentState)s.group[r.ke].activeMonitors[r.mid].currentState={} s.group[r.ke].activeMonitors[r.mid].currentState.mode=r.mode.toString() s.group[r.ke].activeMonitors[r.mid].currentState.fps=r.fps.toString() if(!s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on){ s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on=true }else{ s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on=false } r.mode=req.params.f; try{r.details=JSON.parse(r.details);}catch(er){} r.id=r.mid; s.knexQuery({ action: "update", table: "Monitors", update: { mode: r.mode }, where: [ ['ke','=',r.ke], ['mid','=',r.mid], ] }) s.group[r.ke].rawMonitorConfigurations[r.mid]=r; await s.camera('stop',s.cleanMonitorObject(r)); if(req.params.f!=='stop'){ await s.camera(req.params.f,s.cleanMonitorObject(r)); } s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke); s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke); response.msg = user.lang['Monitor mode changed']+' : '+req.params.f; }else{ response.msg = user.lang['Reset Timer']; } response.cmd_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss'); response.ok = true; if(req.params.ff&&req.params.f!=='stop'){ req.params.ff=parseFloat(req.params.ff); clearTimeout(s.group[r.ke].activeMonitors[r.mid].trigger_timer) switch(req.params.fff){ case'day':case'days': req.timeout=req.params.ff*1000*60*60*24 break; case'hr':case'hour':case'hours': req.timeout=req.params.ff*1000*60*60 break; case'min':case'minute':case'minutes': req.timeout=req.params.ff*1000*60 break; default://seconds req.timeout=req.params.ff*1000 break; } s.group[r.ke].activeMonitors[r.mid].trigger_timer=setTimeout(async function(){ delete(s.group[r.ke].activeMonitors[r.mid].trigger_timer) s.knexQuery({ action: "update", table: "Monitors", update: { mode: s.group[r.ke].activeMonitors[r.mid].currentState.mode }, where: [ ['ke','=',r.ke], ['mid','=',r.mid], ] }) r.neglectTriggerTimer=1; r.mode=s.group[r.ke].activeMonitors[r.mid].currentState.mode; r.fps=s.group[r.ke].activeMonitors[r.mid].currentState.fps; await s.camera('stop',s.cleanMonitorObject(r)); if(s.group[r.ke].activeMonitors[r.mid].currentState.mode!=='stop'){ await s.camera(s.group[r.ke].activeMonitors[r.mid].currentState.mode,s.cleanMonitorObject(r)); } s.group[r.ke].rawMonitorConfigurations[r.mid]=r; s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke); s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke); },req.timeout); // response.end_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss').add(req.timeout,'milliseconds'); } }else{ response.msg = user.lang['Monitor mode is already']+' : '+req.params.f; } }else{ response.msg = user.lang['Monitor or Key does not exist.']; } res.end(s.prettyPrint(response)); }) },res,req); }) /** * API : Get Cloud Video File (proxy) */ app.get(config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file', function (req,res){ s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && !monitorPermissions[`${monitorId}_video_view`] ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); return } var time = s.nameToTime(req.params.file) if(req.query.isUTC === 'true'){ time = s.utcToLocal(time) } time = new Date(time) s.knexQuery({ action: "select", columns: "*", table: "Cloud Videos", where: [ ['ke','=',groupKey], ['mid','=',req.params.id], ['type','=', s.getPostData(req,'type') || 's3'], ['time','=',time] ], limit: 1 },(err,r) => { if(r&&r[0]){ r = r[0] const videoDetails = JSON.parse(r.details) const storageType = videoDetails.type || r.type const onGetVideoData = s.cloudDiskUseOnGetVideoDataExtensions[storageType] if(onGetVideoData){ onGetVideoData(r).then((dataPipe) => { dataPipe.pipe(res) }).catch((err) => { console.error('onGetVideoData ERROR',err,videoDetails) res.status(404) res.end(user.lang['File Not Found in Database']) }) }else{ fetch(r.href).then(actual => { actual.headers.forEach((v, n) => res.setHeader(n, v)); actual.body.pipe(res); }) } }else{ res.status(404) res.end(user.lang['File Not Found in Database']) } }) },res,req); }); /** * API : Get Video File */ const videoRowCaches = {} const videoRowCacheTimeouts = {} app.get(config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file', function (req,res){ s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && !monitorPermissions[`${monitorId}_video_view`] ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); return } var time = s.nameToTime(req.params.file) if(req.query.isUTC === 'true'){ time = s.utcToLocal(time) } time = new Date(time) const cacheName = Object.values(req.params).join('_') const cacheVideoRow = (videoRow) => { videoRowCaches[cacheName] = videoRow clearTimeout(videoRowCacheTimeouts[cacheName]) videoRowCacheTimeouts[cacheName] = setTimeout(() => { delete(videoRowCaches[cacheName]) },60000) } const sendVideo = (videoRow) => { cacheVideoRow(videoRow) const filePath = s.getVideoDirectory(videoRow) + req.params.file fs.stat(filePath,function(err,stats){ if (!err){ if(req.query.json === 'true'){ s.closeJsonResponse(res,videoRow) }else{ s.streamMp4FileOverHttp(filePath,req,res,!!req.query.pureStream) } const clientIp = s.getClientIp(req) s.onVideoAccessExtensions.forEach((extender) => { extender(videoRow,user,groupKey,monitorId,clientIp) }) }else{ s.closeJsonResponse(res,{ ok: false, msg: lang['File Not Found in Filesystem'], err: err }) } }) } if(videoRowCaches[cacheName]){ sendVideo(videoRowCaches[cacheName]) }else{ s.knexQuery({ action: "select", columns: "*", table: "Videos", where: [ ['ke','=',groupKey], ['mid','=',req.params.id], ['time','=',time] ], limit: 1 },(err,r) => { const videoRow = r[0] if(videoRow){ sendVideo(videoRow) }else{ res.status(404) res.end(user.lang['File Not Found in Database']) } }) } },res,req); }); /** * API : Motion Trigger via GET request */ app.get(config.webPaths.apiPrefix+':auth/motion/:ke/:id', function (req,res){ s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId); const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( !monitorConfig || isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed ){ s.closeJsonResponse(res,{ ok: false, msg: !monitorConfig ? user.lang['Monitor or Key does not exist.'] : lang['Not Authorized'] }); return } let simulatedEvent = { id: req.params.id, ke: req.params.ke } if(req.query.data){ try{ Object.assign(simulatedEvent, {details: s.parseJSON(req.query.data)}); }catch(err){ s.closeJsonResponse(res,{ ok: false, msg: user.lang['Data Broken'] }) return } } // fallback for cameras that doesn't support JSON in query parameters ( i.e Sercom ICamera1000 will fail to save HTTP_Notifications as invalid url) else if(req.query.plug && req.query.name && req.query.reason && req.query.confidence) { const { plug, reason, confidence, name, } = req.query; Object.assign(simulatedEvent,{ details: { plug, reason, confidence, name, } }); } else{ s.closeJsonResponse(res,{ ok: false, msg: user.lang['No Data'] }) return } var details = s.group[groupKey].rawMonitorConfigurations[monitorId].details var detectorHttpApi = details.detector_http_api var detectorOn = (details.detector === '1') if( detectorHttpApi === '0' || detectorHttpApi === '2' && !detectorOn || detectorHttpApi === '3' && detectorOn ){ s.closeJsonResponse(res,{ ok: false, msg: user.lang['Trigger Blocked'] }) return; } triggerEvent(simulatedEvent) s.closeJsonResponse(res,{ ok: true, msg: user.lang['Trigger Successful'] }) },res,req) }) /** * API : WebHook Tester */ app.get(config.webPaths.apiPrefix+':auth/hookTester/:ke/:id', function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ s.userLog(req.params,{type:'Test',msg:'Hook Test'}) res.end(s.prettyPrint({ok:true})) },res,req); }) /** * API : Object Detection Counter Status */ app.get(config.webPaths.apiPrefix+':auth/eventCountStatus/:ke/:id', function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], counted: 0, tags: []}); return } var selectedObject = s.group[req.params.ke].activeMonitors[req.params.id].eventsCounted res.end(s.prettyPrint({ ok: true, counted: Object.keys(selectedObject).length, tags: selectedObject, })) },res,req) }) /** * API : Object Detection Counter Status */ app.get([ config.webPaths.apiPrefix+':auth/eventCounts/:ke', config.webPaths.apiPrefix+':auth/eventCounts/:ke/:id' ], function (req,res){ res.setHeader('Content-Type', 'application/json') s.auth(req.params,function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []}); return } s.sqlQueryBetweenTimesWithPermissions({ table: 'Events Counts', user: user, noCount: true, groupKey: req.params.ke, monitorId: req.params.id, startTime: req.query.start, endTime: req.query.end, startTimeOperator: req.query.startOperator, endTimeOperator: req.query.endOperator, limit: req.query.limit, archived: req.query.archived, endIsStartTo: !!req.query.endIsStartTo, parseRowDetails: true, rowName: 'counts', preliminaryValidationFailed: false },(response) => { res.end(s.prettyPrint(response)) }) },res,req) }) /** * API : Camera PTZ Controller */ app.get(config.webPaths.apiPrefix+':auth/control/:ke/:id/:direction', function (req,res){ res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ if(req.params.direction === 'setHome'){ setPresetForCurrentPosition({ id: req.params.id, ke: req.params.ke, },(response) => { res.end(s.prettyPrint(response)) }) }else{ ptzControl(req.params,function(msg){ s.userLog({ id: req.params.id, ke: req.params.ke, },{ msg: msg, direction: req.params.direction, }) res.end(s.prettyPrint(msg)) }) } },res,req); }) /** * API : Upload Video File * API : Add "streamIn" query string to Push to "Dashcam (Streamer v2)" FFMPEG Process */ app.post(config.webPaths.apiPrefix+':auth/videos/:ke/:id',fileupload(), async (req,res) => { var response = {ok:false} res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const groupKey = req.params.ke const monitorId = req.params.id const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user) if( isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed || isRestricted && !monitorPermissions[`${monitorId}_video_delete`] ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); return } // req.query.overwrite === '1' if(s.group[groupKey] && s.group[groupKey].activeMonitors && s.group[groupKey].activeMonitors[monitorId]){ var monitor = s.group[groupKey].rawMonitorConfigurations[monitorId] try { if(!req.files) { res.send({ status: false, message: 'No file uploaded' }); } else { let video = req.files.video; var time = new Date(parseInt(video.name.split('.')[0])) var filename = s.formattedTime(time) + '.' + monitor.ext video.mv(s.getVideoDirectory(monitor) + filename,function(){ s.insertCompletedVideo(monitor,{ file: filename, events: s.group[groupKey].activeMonitors[monitorId].detector_motion_count, endTime: req.body.endTime.indexOf('-') > -1 ? s.nameToTime(req.body.endTime) : parseInt(req.body.endTime) || null, },function(){ response.ok = true response.filename = filename res.end(s.prettyPrint({ ok: true, message: 'File is uploaded', data: { name: video.name, mimetype: video.mimetype, size: video.size } })) }) }); } } catch (err) { response.err = err res.status(500).end(response) } }else{ response.error = 'Non Existant Monitor' res.end(s.prettyPrint(response)) } },res,req); }) /** * API : Modify Video File */ app.get([ config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file/:mode', config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file/:mode/:f', config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode', config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode/:f' ], function (req,res){ let response = { ok: false }; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId) const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed || isRestricted && !monitorPermissions[`${monitorId}_video_delete`] ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); return } var time = s.nameToTime(req.params.file) time = new Date(time) var origURL = req.originalUrl.split('/') var videoParam = origURL[origURL.indexOf(req.params.auth) + 1] var videoSet = 'Videos' switch(videoParam){ case'cloudVideos': videoSet = 'Cloud Videos' break; } const whereQuery = [ ['ke','=',groupKey], ['mid','=',req.params.id], ['time','=',time] ] if(videoParam === 'cloudVideos')whereQuery.push(['type','=',s.getPostData(req,'type') || 's3']); s.knexQuery({ action: "select", columns: "*", table: videoSet, where: whereQuery, limit: 1 },async (err,r) => { if(r && r[0]){ r=r[0]; const originalFileName = `${s.formattedTime(r.time)+'.'+r.ext}` var details = s.parseJSON(r.details) || {} switch(req.params.mode){ case'slice': const startTime = s.getPostData(req,'startTime'); const endTime = s.getPostData(req,'endTime'); const sliceResponse = await sliceVideo(r,{ startTime: startTime, endTime: endTime, }); response = sliceResponse break; case'archive': response.ok = true const unarchive = s.getPostData(req,'unarchive') == '1'; const archiveResponse = await archiveVideo(r,unarchive) response.ok = archiveResponse.ok response.archived = archiveResponse.archived break; case'fix': await reEncodeVideoAndReplace(r) break; case'compress': response.ok = true reEncodeVideoAndBinOriginalAddToQueue({ video: r, targetExtension: 'webm', doSlowly: false }).then((encodeResponse) => { s.debugLog('Complete Compression',encodeResponse) }).catch((err) => { console.log(err) }) break; case'status': r.f = 'video_edit' switch(videoParam){ case'cloudVideos': r.f += '_cloud' break; } r.status = parseInt(req.params.f) if(isNaN(req.params.f)||req.params.f===0){ response.msg = 'Not a valid value.'; }else{ response.ok = true; s.knexQuery({ action: "update", table: videoSet, update: { status: req.params.f }, where: [ ['ke','=',groupKey], ['mid','=',req.params.id], ['time','=',time] ] }) s.tx(r,'GRP_'+r.ke); } break; case'delete': response.ok = true; switch(videoParam){ case'cloudVideos': s.deleteVideoFromCloud(r,details.type || r.type || 's3') break; default: s.deleteVideo(r) break; } break; default: response.msg = user.lang.modifyVideoText1; break; } }else{ response.msg = user.lang['No such file']; } res.end(s.prettyPrint(response)); }) },res,req); }); /** * API : Merge Videos and Bin it */ app.post(config.webPaths.apiPrefix+':auth/mergeVideos/:ke/:id', function (req,res){ s.auth(req.params, async function(user){ const monitorId = req.params.id const groupKey = req.params.ke const { monitorPermissions, monitorRestrictions, } = s.getMonitorsPermitted(user.details,monitorId,'video_view') const { isRestricted, isRestrictedApiKey, apiKeyPermissions, } = s.checkPermission(user); if( isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed || isRestricted && ( monitorId && !monitorPermissions[`${monitorId}_video_view`] || monitorRestrictions.length === 0 ) ){ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []}); return } const response = { ok: false } const selectedVideos = s.getPostData(req,'videos'); console.log('selected',selectedVideos) if(selectedVideos && selectedVideos.length > 1){ const mergedFilePath = await mergeVideosAndBin(selectedVideos); response.ok = !!mergedFilePath; s.closeJsonResponse(res, response); }else{ s.sqlQueryBetweenTimesWithPermissions({ table: 'Videos', user: user, noCount: true, groupKey, monitorId, startTime: s.getPostData(req,'start'), endTime: s.getPostData(req,'end'), startTimeOperator: s.getPostData(req,'startOperator'), endTimeOperator: s.getPostData(req,'endOperator'), noLimit: s.getPostData(req,'noLimit'), limit: s.getPostData(req,'limit'), archived: s.getPostData(req,'archived'), endIsStartTo: !!s.getPostData(req,'endIsStartTo'), parseRowDetails: false, rowName: 'videos', monitorRestrictions: monitorRestrictions, preliminaryValidationFailed: false }, async ({ videos }) => { if(videos){ const mergedFilePath = await mergeVideosAndBin(videos); response.ok = !!mergedFilePath; } s.closeJsonResponse(res, response); }) } },res,req); }); /** * API : Get Login Tokens */ app.get(config.webPaths.apiPrefix+':auth/loginTokens/:ke', function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,(user) => { const groupKey = req.params.ke s.knexQuery({ action: "select", columns: "*", table: "LoginTokens", where: [ ['ke','=',groupKey], ['uid','=',user.uid], ] },(err,rows) => { response.ok = true response.rows = rows s.closeJsonResponse(res,response) }) },res,req); }); /** * API : Get Login Token */ app.get(config.webPaths.apiPrefix+':auth/loginTokens/:ke/:loginId', function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,(user) => { const groupKey = req.params.ke s.knexQuery({ action: "select", columns: "*", table: "LoginTokens", where: [ ['loginId','=',user.uid], ['ke','=',groupKey], ['uid','=',user.uid], ], limit: 1 },(err,rows) => { response.ok = !!rows[0] response.row = rows[0] s.closeJsonResponse(res,response) }) },res,req); }); /** * API : Delete Login Token */ app.get(config.webPaths.apiPrefix+':auth/loginTokens/:ke/:loginId/delete', function (req,res){ var response = {ok:false}; res.setHeader('Content-Type', 'application/json'); s.auth(req.params,async (user) => { const loginId = req.params.loginId const groupKey = req.params.ke const deleteResponse = await s.knexQueryPromise({ action: "delete", table: "LoginTokens", where: [ ['loginId','=',loginId], ['ke','=',groupKey], ['uid','=',user.uid], ] }) response.ok = true s.closeJsonResponse(res,response) },res,req); }); /** * API : Stream In to push data to ffmpeg by HTTP */ app.all('/:auth/streamIn/:ke/:id', function (req, res) { s.auth(req.params,function(user){ const ipAddress = s.getClientIp(req) const groupKey = req.params.ke const monitorId = req.params.id const timeStartedConnection = new Date(); s.userLog({ ke: groupKey, mid: monitorId, },{ type: "HTTP streamIn Started", msg: { ipAddress: ipAddress, } }) res.connection.setTimeout(0); req.on('data', function(buffer){ s.group[groupKey].activeMonitors[monitorId].spawn.stdin.write(buffer) }); req.on('end',function(){ s.userLog({ ke: groupKey, mid: monitorId, },{ type: "HTTP streamIn Closed", msg: { timeStartedConnection: timeStartedConnection, ipAddress: ipAddress, } }) }); },res,req) }) /** * API : Account Edit from Dashboard */ app.all(config.webPaths.apiPrefix+':auth/accounts/:ke/edit',function (req,res){ s.auth(req.params,function(user){ var endData = { ok : false } var form = s.getPostData(req) if(form){ endData.ok = true s.accountSettingsEdit({ ke: req.params.ke, uid: user.uid, form: form, cnid: user.cnid }) }else{ endData.msg = lang.postDataBroken } s.closeJsonResponse(res,endData) },res,req) }) /** * API : Get Definitions JSON */ app.get(config.webPaths.apiPrefix+':auth/definitions/:ke',function (req,res){ s.auth(req.params,function(user){ var endData = { ok: true, definitions: s.getDefinitonFile(user.details.lang) } s.closeJsonResponse(res,endData) },res,req) }) /** * API : Get Language JSON */ app.get(config.webPaths.apiPrefix+':auth/language/:ke',function (req,res){ s.auth(req.params,function(user){ var endData = { ok: true, definitions: s.getLanguageFile(user.details.lang) } s.closeJsonResponse(res,endData) },res,req) }) /** * Robots.txt */ app.get('/robots.txt', function (req,res){ res.on('finish',function(){ res.end() }) fs.createReadStream(s.mainDirectory + '/web/pages/robots.txt').pipe(res) }) }