Shinobi/libs/webServerPaths.js

2256 lines
94 KiB
JavaScript

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)
})
}