Merge branch 'google-login' into 'dev'
Google Sign-In Button and Login Mechanism Partial Rewrite See merge request Shinobi-Systems/Shinobi!297auto-build-api-doc-with-code
commit
cf588837dd
|
@ -85,6 +85,8 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => {
|
|||
require('./libs/scheduler.js')(s,config,lang,app,io)
|
||||
//onvif device manager
|
||||
require('./libs/onvifDeviceManager.js')(s,config,lang,app,io)
|
||||
//alternate logins
|
||||
require('./libs/auth/logins.js')(s,config,lang,app)
|
||||
//on-start actions, daemon(s) starter
|
||||
await require('./libs/startup.js')(s,config,lang)
|
||||
//p2p, commander
|
||||
|
|
|
@ -4215,6 +4215,17 @@ module.exports = function(s,config,lang){
|
|||
},
|
||||
]
|
||||
},
|
||||
"AlternateLogins": {
|
||||
"name": lang["Alternate Logins"],
|
||||
"color": "orange",
|
||||
"info": [
|
||||
{
|
||||
"form-group-class-pre-layer": "form-group",
|
||||
"fieldType": 'div',
|
||||
"id": "alternate-logins"
|
||||
},
|
||||
]
|
||||
},
|
||||
"2-Factor Authentication": {
|
||||
"name": lang['2-Factor Authentication'],
|
||||
"color": "grey",
|
||||
|
|
|
@ -113,7 +113,18 @@
|
|||
"Never": "Never",
|
||||
"API": "API",
|
||||
"ONVIF": "ONVIF",
|
||||
"Alternate Logins": "Alternate Logins",
|
||||
"Unlink": "Unlink",
|
||||
"Unlinked": "Unlinked",
|
||||
"Unlink Login": "Unlink Login?",
|
||||
"lastLogin": "Last Login",
|
||||
"Home": "Home",
|
||||
"alreadyLinked": "Already Linked to an Account",
|
||||
"Link Google Account": "Link Google Account",
|
||||
"noLoginTokensAdded": "There are no Alternate Logins associated to this account.",
|
||||
"tokenNotUserBound": "This Login Handle is not linked to a user on this server!",
|
||||
"tokenNotUserBoundPt2": "Type your credentials then use the Google Sign-In button to link quickly.",
|
||||
"loginHandleUnbound": "Login has been unlinked from this account.",
|
||||
"Set Home": "Set Home",
|
||||
"Set Home Position (ONVIF-only)": "Set Home Position (ONVIF-only)",
|
||||
"Non-Standard ONVIF": "Non-Standard ONVIF",
|
||||
|
|
26
libs/auth.js
26
libs/auth.js
|
@ -5,6 +5,7 @@ module.exports = function(s,config,lang){
|
|||
s.superUsersApi = {}
|
||||
s.factorAuth = {}
|
||||
s.failedLoginAttempts = {}
|
||||
s.alternateLogins = {}
|
||||
//
|
||||
var getUserByUid = function(params,columns,callback){
|
||||
if(!columns)columns = '*'
|
||||
|
@ -247,39 +248,18 @@ module.exports = function(s,config,lang){
|
|||
if(userSelected){
|
||||
resp.$user = userSelected
|
||||
}
|
||||
if(adminUsersSelected){
|
||||
resp.users = adminUsersSelected
|
||||
}
|
||||
}
|
||||
callback({
|
||||
ip : ip,
|
||||
$user: userSelected,
|
||||
users: adminUsersSelected,
|
||||
config: chosenConfig,
|
||||
lang: lang
|
||||
})
|
||||
}
|
||||
var foundUser = function(){
|
||||
if(params.users === true){
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Users",
|
||||
where: [
|
||||
['details','NOT LIKE','%"sub"%'],
|
||||
]
|
||||
},(err,r) => {
|
||||
adminUsersSelected = r
|
||||
success()
|
||||
})
|
||||
}else{
|
||||
success()
|
||||
}
|
||||
}
|
||||
if(params.auth && Object.keys(s.superUsersApi).indexOf(params.auth) > -1){
|
||||
userFound = true
|
||||
userSelected = s.superUsersApi[params.auth].$user
|
||||
foundUser()
|
||||
success()
|
||||
}else{
|
||||
var superUserList = JSON.parse(fs.readFileSync(s.location.super))
|
||||
superUserList.forEach(function(superUser,n){
|
||||
|
@ -300,7 +280,7 @@ module.exports = function(s,config,lang){
|
|||
){
|
||||
userFound = true
|
||||
userSelected = superUser
|
||||
foundUser()
|
||||
success()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
module.exports = (s,config,lang) => {
|
||||
async function getLoginToken(loginId,bindType) {
|
||||
bindType = bindType ? bindType : 'google'
|
||||
return (await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: '*',
|
||||
table: "LoginTokens",
|
||||
where: [
|
||||
['loginId','=',`${bindType}-${loginId}`],
|
||||
['type','=',bindType],
|
||||
],
|
||||
limit: 1
|
||||
})).rows[0]
|
||||
}
|
||||
async function bindLoginIdToUser(options) {
|
||||
const response = {ok: false}
|
||||
const loginId = options.loginId
|
||||
const groupKey = options.ke
|
||||
const userId = options.uid
|
||||
const name = options.name
|
||||
const bindType = options.type ? options.type : 'google'
|
||||
const searchResponse = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: '*',
|
||||
table: "LoginTokens",
|
||||
where: [
|
||||
['loginId','=',`${bindType}-${loginId}`],
|
||||
['type','=',bindType],
|
||||
]
|
||||
})
|
||||
if(!searchResponse.rows[0]){
|
||||
const insertResponse = await s.knexQueryPromise({
|
||||
action: "insert",
|
||||
table: "LoginTokens",
|
||||
insert: {
|
||||
loginId: `${bindType}-${loginId}`,
|
||||
type: bindType,
|
||||
ke: groupKey,
|
||||
uid: userId,
|
||||
name: name,
|
||||
lastLogin: new Date(),
|
||||
}
|
||||
})
|
||||
response.ok = insertResponse.ok
|
||||
}else{
|
||||
response.msg = lang.alreadyLinked
|
||||
}
|
||||
return response
|
||||
}
|
||||
async function refreshLoginTokenAccessDate(loginId,bindType) {
|
||||
const response = {ok: false}
|
||||
bindType = bindType ? bindType : 'google'
|
||||
const updateResponse = await s.knexQueryPromise({
|
||||
action: "update",
|
||||
table: "LoginTokens",
|
||||
update: {
|
||||
lastLogin: new Date()
|
||||
},
|
||||
where: [
|
||||
['loginId','=',`${bindType}-${loginId}`],
|
||||
['type','=',bindType],
|
||||
]
|
||||
})
|
||||
response.ok = updateResponse.ok
|
||||
return response
|
||||
}
|
||||
async function deleteLoginToken(loginId) {
|
||||
const response = {ok: false}
|
||||
bindType = bindType ? bindType : 'google'
|
||||
const updateResponse = await s.knexQueryPromise({
|
||||
action: "delete",
|
||||
table: "LoginTokens",
|
||||
where: [
|
||||
['loginId','=',`${bindType}-${loginId}`],
|
||||
['type','=',bindType],
|
||||
]
|
||||
})
|
||||
response.ok = updateResponse.ok
|
||||
return response
|
||||
}
|
||||
return {
|
||||
getLoginToken: getLoginToken,
|
||||
deleteLoginToken: deleteLoginToken,
|
||||
bindLoginIdToUser: bindLoginIdToUser,
|
||||
refreshLoginTokenAccessDate: refreshLoginTokenAccessDate,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
const {OAuth2Client} = require('google-auth-library');
|
||||
module.exports = (s,config,lang,app) => {
|
||||
const {
|
||||
basicAuth,
|
||||
} = require('./utils.js')(s,config,lang)
|
||||
const {
|
||||
getLoginToken,
|
||||
deleteLoginToken,
|
||||
bindLoginIdToUser,
|
||||
refreshLoginTokenAccessDate,
|
||||
} = require('./alternateLogins.js')(s,config,lang)
|
||||
console.error(`Google App ID : ${config.appIdGoogleSignIn}`)
|
||||
const client = new OAuth2Client(config.appIdGoogleSignIn);
|
||||
async function verifyToken(userLoginToken) {
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken: userLoginToken,
|
||||
audience: config.appIdGoogleSignIn,
|
||||
});
|
||||
const payload = ticket.getPayload();
|
||||
const userid = payload['sub'];
|
||||
return {
|
||||
ok: !!payload.email,
|
||||
user: payload.email ? {
|
||||
id: userid,
|
||||
name: payload.name,
|
||||
email: payload.email,
|
||||
picture: payload.picture,
|
||||
} : null,
|
||||
}
|
||||
}
|
||||
async function loginWithGoogleAccount(userLoginToken) {
|
||||
const response = {ok: false, googleSignedIn: false}
|
||||
const tokenResponse = await verifyToken(userLoginToken)
|
||||
if(tokenResponse.ok){
|
||||
const user = tokenResponse.user
|
||||
response.googleSignedIn = true
|
||||
response.googleUser = user
|
||||
const foundToken = await getLoginToken(user.id,'google')
|
||||
if(foundToken){
|
||||
const userResponse = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: '*',
|
||||
table: "Users",
|
||||
where: [
|
||||
['uid','=',foundToken.uid],
|
||||
['ke','=',foundToken.ke],
|
||||
],
|
||||
limit: 1
|
||||
})
|
||||
response.ok = true
|
||||
userResponse.rows[0].details = s.parseJSON(userResponse.rows[0].details)
|
||||
response.user = userResponse.rows[0]
|
||||
}else{
|
||||
response.msg = lang.tokenNotUserBound
|
||||
if(config.allowBindingAltLoginsFromLoginPage){
|
||||
response.msg += '\n' + lang.tokenNotUserBoundPt2
|
||||
}
|
||||
// make new if no users?
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
s.onProcessReady(() => {
|
||||
config.renderPaths.loginTokenAddGoogle = `pages/loginTokenAddGoogle`
|
||||
s.alternateLogins['google'] = async (params) => {
|
||||
const response = { ok: false }
|
||||
const loginToken = params.alternateLoginToken
|
||||
const username = params.mail
|
||||
const password = params.pass
|
||||
const googleLoginResponse = await loginWithGoogleAccount(loginToken)
|
||||
if(googleLoginResponse.user){
|
||||
const user = googleLoginResponse.user
|
||||
response.ok = true
|
||||
response.user = user
|
||||
refreshLoginTokenAccessDate(googleLoginResponse.googleUser.id,'google')
|
||||
}else if(config.allowBindingAltLoginsFromLoginPage && googleLoginResponse.googleSignedIn && username && password){
|
||||
const basicAuthResponse = await basicAuth(username,password)
|
||||
if(basicAuthResponse.user){
|
||||
const user = basicAuthResponse.user
|
||||
const loginId = googleLoginResponse.googleUser.id
|
||||
const groupKey = user.ke
|
||||
const userId = user.uid
|
||||
const bindResponse = await bindLoginIdToUser({
|
||||
loginId: loginId,
|
||||
ke: groupKey,
|
||||
uid: userId,
|
||||
name: googleLoginResponse.googleUser.name,
|
||||
type: 'google'
|
||||
})
|
||||
response.ok = true
|
||||
response.user = basicAuthResponse.user
|
||||
}
|
||||
}else{
|
||||
response.msg = googleLoginResponse.msg
|
||||
}
|
||||
return response
|
||||
}
|
||||
s.definitions["Account Settings"].blocks["AlternateLogins"].info.push({
|
||||
"form-group-class-pre-layer": "form-group",
|
||||
"fieldType": "btn",
|
||||
"class": `btn-info google-sign-in`,
|
||||
"btnContent": `<i class="fa fa-google"></i> ${lang['Link Google Account']}`,
|
||||
})
|
||||
s.customAutoLoadTree['LibsJs'].push(`dash2.googleSignIn.js`)
|
||||
})
|
||||
/**
|
||||
* API : Add Token Window (Sign-In to Google) (GET)
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/loginTokenAddGoogle/:ke', function (req,res){
|
||||
s.auth(req.params,(user) => {
|
||||
s.renderPage(req,res,config.renderPaths.loginTokenAddGoogle,{
|
||||
lang: lang,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
$user: user
|
||||
})
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Add Token Window (Sign-In to Google) (POST)
|
||||
*/
|
||||
app.post(config.webPaths.apiPrefix+':auth/loginTokenAddGoogle/:ke', function (req,res){
|
||||
const response = {ok: false};
|
||||
s.auth(req.params,async (user) => {
|
||||
const userId = user.uid
|
||||
const groupKey = req.params.ke
|
||||
const loginToken = req.body.loginToken
|
||||
const tokenResponse = await verifyToken(loginToken)
|
||||
if(tokenResponse.ok){
|
||||
const googleUser = tokenResponse.user
|
||||
const loginId = googleUser.id
|
||||
const bindResponse = await bindLoginIdToUser({
|
||||
loginId: loginId,
|
||||
ke: groupKey,
|
||||
uid: userId,
|
||||
name: googleUser.name,
|
||||
type: 'google'
|
||||
})
|
||||
response.ok = bindResponse.ok
|
||||
response.msg = bindResponse.msg
|
||||
}
|
||||
s.closeJsonResponse(res,response)
|
||||
},res,req);
|
||||
});
|
||||
return {
|
||||
client: client,
|
||||
verifyToken: verifyToken,
|
||||
loginWithGoogleAccount: loginWithGoogleAccount,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = (s,config,lang,app) => {
|
||||
s.debugLog('!!!!!!!!!')
|
||||
s.debugLog('Loading Alternate Login Methods...')
|
||||
if(config.allowGoogleSignOn){
|
||||
s.debugLog('Google')
|
||||
require('./google.js')(s,config,lang,app)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
function basicAuth(username,password){
|
||||
const response = { ok: false }
|
||||
return new Promise((resolve,reject) => {
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Users",
|
||||
where: [
|
||||
['mail','=',username],
|
||||
['pass','=',s.createHash(password)],
|
||||
],
|
||||
limit: 1
|
||||
},(err,r) => {
|
||||
if(!err && r && r[0]){
|
||||
const user = r[0]
|
||||
response.ok = true
|
||||
user.details = s.parseJSON(user.details)
|
||||
response.user = user
|
||||
}else{
|
||||
response.err = err
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
// async function adminAuth(username,password){
|
||||
// const response = { ok: false }
|
||||
// const basicAuthResponse = await basicAuth(username,password)
|
||||
// const user = basicAuthResponse.user
|
||||
// if(user && !user.details.sub){
|
||||
// response.ok = true
|
||||
// response.user = user
|
||||
// }
|
||||
// return response
|
||||
// }
|
||||
function superUserAuth(params){
|
||||
const response = { ok: false }
|
||||
if(!fs.existsSync(s.location.super)){
|
||||
response.msg = lang.superAdminText
|
||||
}else{
|
||||
const authToken = params.auth
|
||||
const username = params.mail
|
||||
const password = params.pass
|
||||
let userFound = false
|
||||
let userSelected = false
|
||||
try{
|
||||
if(authToken && Object.keys(s.superUsersApi).indexOf(authToken) > -1){
|
||||
userFound = true
|
||||
userSelected = s.superUsersApi[authToken].$user
|
||||
}else{
|
||||
var superUserList = JSON.parse(fs.readFileSync(s.location.super))
|
||||
superUserList.forEach(function(superUser,n){
|
||||
if(
|
||||
userFound === false &&
|
||||
(
|
||||
authToken && superUser.tokens && superUser.tokens[authToken] || //using API key (object)
|
||||
authToken && superUser.tokens && superUser.tokens.indexOf && superUser.tokens.indexOf(authToken) > -1 || //using API key (array)
|
||||
(
|
||||
username && username.toLowerCase() === superUser.mail.toLowerCase() && //email matches
|
||||
(
|
||||
password === superUser.pass || //user give it already hashed
|
||||
superUser.pass === s.createHash(password) || //hash and check it
|
||||
superUser.pass.toLowerCase() === s.md5(password).toLowerCase() //check if still using md5
|
||||
)
|
||||
)
|
||||
)
|
||||
){
|
||||
userFound = true
|
||||
userSelected = superUser
|
||||
}
|
||||
})
|
||||
}
|
||||
}catch(err){
|
||||
s.systemLog('The following error may mean your super.json is not formatted correctly.')
|
||||
s.systemLog('You can reset it by replacing it with the super.sample.json file.')
|
||||
console.error(`super.json error`)
|
||||
console.error(err)
|
||||
}
|
||||
if(userFound){
|
||||
response.ok = true
|
||||
response.user = userSelected
|
||||
}else{
|
||||
response.msg = lang['Not Authorized']
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
function superLogin(username,password){
|
||||
return new Promise((resolve,reject) => {
|
||||
const response = { ok: false }
|
||||
const authResponse = superUserAuth({
|
||||
mail: username,
|
||||
pass: password,
|
||||
})
|
||||
if(authResponse.ok){
|
||||
response.ok = true
|
||||
response.user = authResponse.user
|
||||
}else{
|
||||
response.msg = lang['Not Authorized']
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
}
|
||||
function createTwoFactorAuth(user,machineId,pageTarget){
|
||||
const userDetails = user.details
|
||||
const response = {
|
||||
ok: true,
|
||||
hasItEnabled: userDetails.factorAuth === "1",
|
||||
isAnAcceptedMachineId: false,
|
||||
goToDashboard: false,
|
||||
}
|
||||
if(response.hasItEnabled){
|
||||
if(!userDetails.acceptedMachines||!(userDetails.acceptedMachines instanceof Object)){
|
||||
userDetails.acceptedMachines={}
|
||||
}
|
||||
if(!userDetails.acceptedMachines[machineId]){
|
||||
if(!s.factorAuth[user.ke]){s.factorAuth[user.ke]={}}
|
||||
if(!s.factorAuth[user.ke][user.uid]){
|
||||
s.factorAuth[user.ke][user.uid] = {
|
||||
key: s.nid(),
|
||||
user: user
|
||||
}
|
||||
s.onTwoFactorAuthCodeNotificationExtensions.forEach(function(extender){
|
||||
extender(user)
|
||||
})
|
||||
}
|
||||
const factorAuthObject = s.factorAuth[user.ke][user.uid]
|
||||
factorAuthObject.function = pageTarget
|
||||
factorAuthObject.info = {
|
||||
ok: true,
|
||||
auth_token: user.auth,
|
||||
ke: user.ke,
|
||||
uid: user.uid,
|
||||
mail: user.mail,
|
||||
details: user.details
|
||||
}
|
||||
clearTimeout(factorAuthObject.expireAuth)
|
||||
factorAuthObject.expireAuth = setTimeout(function(){
|
||||
s.deleteFactorAuth(user)
|
||||
},1000*60*15)
|
||||
}else{
|
||||
response.isAnAcceptedMachineId = true
|
||||
}
|
||||
}
|
||||
if(!response.hasItEnabled || response.isAnAcceptedMachineId){
|
||||
response.goToDashboard = true
|
||||
}
|
||||
return response
|
||||
}
|
||||
function twoFactorVerification(params){
|
||||
const response = { ok: false }
|
||||
const factorAuthKey = (params.factorAuthKey || '00').trim()
|
||||
console.log(params)
|
||||
console.log(s.factorAuth[params.ke][params.id])
|
||||
if(
|
||||
s.factorAuth[params.ke] &&
|
||||
s.factorAuth[params.ke][params.id] &&
|
||||
s.factorAuth[params.ke][params.id].key === factorAuthKey
|
||||
){
|
||||
const factorAuthObject = s.factorAuth[params.ke][params.id]
|
||||
// if(factorAuthObject.key===params.factorAuthKey){
|
||||
const userDetails = factorAuthObject.info.details
|
||||
if(params.remember==="1"){
|
||||
if(!userDetails.acceptedMachines||!(userDetails.acceptedMachines instanceof Object)){
|
||||
userDetails.acceptedMachines={}
|
||||
}
|
||||
if(!userDetails.acceptedMachines[params.machineID]){
|
||||
userDetails.acceptedMachines[params.machineID]={}
|
||||
s.knexQuery({
|
||||
action: "update",
|
||||
table: "Users",
|
||||
update: {
|
||||
details: JSON.stringify(userDetails)
|
||||
},
|
||||
where: [
|
||||
['ke','=',params.ke],
|
||||
['uid','=',params.id],
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
const pageTarget = factorAuthObject.function
|
||||
factorAuthObject.info.lang = s.getLanguageFile(userDetails.lang)
|
||||
response.info = Object.assign(factorAuthObject.info,{})
|
||||
clearTimeout(factorAuthObject.expireAuth)
|
||||
s.deleteFactorAuth({
|
||||
ke: params.ke,
|
||||
uid: params.id,
|
||||
})
|
||||
// }else{
|
||||
// var info = factorAuthObject.info
|
||||
// renderPage(config.renderPaths.factorAuth,{$user:{
|
||||
// ke: info.ke,
|
||||
// id: info.uid,
|
||||
// mail: info.mail,
|
||||
// },lang:req.lang});
|
||||
// res.end();
|
||||
// }
|
||||
response.pageTarget = pageTarget
|
||||
response.ok = true
|
||||
}
|
||||
return response
|
||||
}
|
||||
function ldapLogin(username,password){
|
||||
|
||||
}
|
||||
return {
|
||||
basicAuth: basicAuth,
|
||||
superUserAuth: superUserAuth,
|
||||
superLogin: superLogin,
|
||||
createTwoFactorAuth: createTwoFactorAuth,
|
||||
twoFactorVerification: twoFactorVerification,
|
||||
ldapLogin: ldapLogin,
|
||||
}
|
||||
}
|
27
libs/sql.js
27
libs/sql.js
|
@ -40,7 +40,7 @@ module.exports = function(s,config){
|
|||
s.sqlQuery = sqlQuery
|
||||
s.connectDatabase = connectDatabase
|
||||
s.sqlQueryBetweenTimesWithPermissions = sqlQueryBetweenTimesWithPermissions
|
||||
s.preQueries = function(){
|
||||
s.preQueries = async function(){
|
||||
var knex = s.databaseEngine
|
||||
var mySQLtail = ''
|
||||
if(config.databaseType === 'mysql'){
|
||||
|
@ -133,6 +133,31 @@ module.exports = function(s,config){
|
|||
}
|
||||
},true)
|
||||
}
|
||||
try{
|
||||
s.databaseEngine.schema.createTable('LoginTokens', table => {
|
||||
table.string('loginId',255).defaultTo('')
|
||||
table.string('type',25).defaultTo('')
|
||||
table.string('ke',50).defaultTo('')
|
||||
table.string('uid',50).defaultTo('')
|
||||
table.string('name',50).defaultTo('Unknown')
|
||||
table.timestamp('lastLogin').defaultTo(s.databaseEngine.fn.now())
|
||||
}).then(() => {
|
||||
s.databaseEngine.schema.alterTable('LoginTokens', function(table) {
|
||||
table.unique('loginId')
|
||||
}).then(() => {
|
||||
s.systemLog('Created New Database Table : LoginTokens')
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}).catch((err) => {
|
||||
if(err && err.code !== 'ER_TABLE_EXISTS_ERROR'){
|
||||
console.log('error')
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
delete(s.preQueries)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ module.exports = function(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)
|
||||
if(config.productType === 'Pro'){
|
||||
var LdapAuth = require('ldapauth-fork');
|
||||
}
|
||||
|
@ -160,10 +167,10 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.checkCorrectPathEnding(config.webPaths.home)+':screen',
|
||||
s.checkCorrectPathEnding(config.webPaths.admin)+':screen',
|
||||
s.checkCorrectPathEnding(config.webPaths.super)+':screen',
|
||||
],function (req,res){
|
||||
],async function (req,res){
|
||||
var response = {ok: false};
|
||||
req.ip = s.getClientIp(req)
|
||||
var screenChooser = function(screen){
|
||||
const screenChooser = function(screen){
|
||||
var search = function(screen){
|
||||
if(req.url.indexOf(screen) > -1){
|
||||
return true
|
||||
|
@ -198,7 +205,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
return false
|
||||
}
|
||||
//
|
||||
renderPage = function(focus,data){
|
||||
const renderPage = function(focus,data){
|
||||
if(s.failedLoginAttempts[req.body.mail]){
|
||||
clearTimeout(s.failedLoginAttempts[req.body.mail].timeout)
|
||||
delete(s.failedLoginAttempts[req.body.mail])
|
||||
|
@ -213,22 +220,32 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.renderPage(req,res,focus,data)
|
||||
}
|
||||
}
|
||||
failedAuthentication = function(board){
|
||||
const failedAuthentication = function(board,failIdentifier,failMessage){
|
||||
// brute protector
|
||||
if(!s.failedLoginAttempts[req.body.mail]){
|
||||
s.failedLoginAttempts[req.body.mail] = {
|
||||
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[req.body.mail].failCount
|
||||
if(!s.failedLoginAttempts[req.body.mail].ips[req.ip]){
|
||||
s.failedLoginAttempts[req.body.mail].ips[req.ip] = 0
|
||||
++s.failedLoginAttempts[failIdentifier].failCount
|
||||
if(!s.failedLoginAttempts[failIdentifier].ips[req.ip]){
|
||||
s.failedLoginAttempts[failIdentifier].ips[req.ip] = 0
|
||||
}
|
||||
++s.failedLoginAttempts[req.body.mail].ips[req.ip]
|
||||
clearTimeout(s.failedLoginAttempts[req.body.mail].timeout)
|
||||
s.failedLoginAttempts[req.body.mail].timeout = setTimeout(function(){
|
||||
delete(s.failedLoginAttempts[req.body.mail])
|
||||
++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'){
|
||||
|
@ -237,7 +254,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
}else{
|
||||
s.renderPage(req,res,config.renderPaths.index,{
|
||||
failedLogin: true,
|
||||
message: lang.failedLoginText2,
|
||||
message: failMessage || lang.failedLoginText2,
|
||||
lang: s.copySystemDefaultLanguage(),
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
screen: screenChooser(req.params.screen)
|
||||
|
@ -251,7 +268,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
type: lang['Authentication Failed'],
|
||||
msg: {
|
||||
for: board,
|
||||
mail: req.body.mail,
|
||||
mail: failIdentifier,
|
||||
ip: req.ip
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +280,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
columns: "ke,uid,details",
|
||||
table: "Users",
|
||||
where: [
|
||||
['mail','=',req.body.mail],
|
||||
['mail','=',failIdentifier],
|
||||
]
|
||||
},(err,r) => {
|
||||
if(r && r[0]){
|
||||
|
@ -278,59 +295,40 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
}
|
||||
}
|
||||
checkRoute = function(r){
|
||||
switch(req.body.function){
|
||||
function checkRoute(pageTarget,userInfo){
|
||||
if(!userInfo.lang){
|
||||
userInfo.lang = s.getLanguageFile(userInfo.details.lang)
|
||||
}
|
||||
switch(pageTarget){
|
||||
case'cam':
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Monitors",
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
['type','=','dashcam'],
|
||||
]
|
||||
},(err,rr) => {
|
||||
response.mons = rr
|
||||
renderPage(config.renderPaths.dashcam,{
|
||||
// config: s.getConfigWithBranding(req.hostname),
|
||||
$user: response,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
$user: userInfo,
|
||||
lang: userInfo.lang,
|
||||
define: s.getDefinitonFile(userInfo.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
break;
|
||||
case'streamer':
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Monitors",
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
['type','=','socket'],
|
||||
]
|
||||
},(err,rr) => {
|
||||
response.mons=rr;
|
||||
renderPage(config.renderPaths.streamer,{
|
||||
// config: s.getConfigWithBranding(req.hostname),
|
||||
$user: response,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
$user: userInfo,
|
||||
lang: userInfo.lang,
|
||||
define: s.getDefinitonFile(userInfo.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
break;
|
||||
case'admin':
|
||||
default:
|
||||
var chosenRender = 'home'
|
||||
if(r.details.sub && r.details.landing_page && r.details.landing_page !== '' && config.renderPaths[r.details.landing_page]){
|
||||
chosenRender = r.details.landing_page
|
||||
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: response,
|
||||
$user: userInfo,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
lang:r.lang,
|
||||
define:s.getDefinitonFile(r.details.lang),
|
||||
lang: userInfo.lang,
|
||||
define: s.getDefinitonFile(userInfo.details.lang),
|
||||
addStorage: s.dir.addStorage,
|
||||
fs: fs,
|
||||
__dirname: s.mainDirectory,
|
||||
|
@ -338,109 +336,116 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
break;
|
||||
}
|
||||
s.userLog({ke:r.ke,mid:'$USER'},{type:r.lang['New Authentication Token'],msg:{for:req.body.function,mail:r.mail,id:r.uid,ip:req.ip}})
|
||||
// res.end();
|
||||
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.mail&&req.body.pass){
|
||||
req.default=function(){
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Users",
|
||||
where: [
|
||||
['mail','=',req.body.mail],
|
||||
['pass','=',s.createHash(req.body.pass)],
|
||||
],
|
||||
limit: 1
|
||||
},(err,r) => {
|
||||
if(!err && r && r[0]){
|
||||
r=r[0];r.auth=s.md5(s.gid());
|
||||
})
|
||||
}
|
||||
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: r.auth
|
||||
auth: sessionKey
|
||||
},
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
['uid','=',r.uid],
|
||||
['ke','=',user.ke],
|
||||
['uid','=',user.uid],
|
||||
]
|
||||
})
|
||||
response = {
|
||||
checkRoute(req.body.function,{
|
||||
ok: true,
|
||||
auth_token: r.auth,
|
||||
ke: r.ke,
|
||||
uid: r.uid,
|
||||
mail: r.mail,
|
||||
details: r.details
|
||||
};
|
||||
r.details = JSON.parse(r.details);
|
||||
r.lang = s.getLanguageFile(r.details.lang)
|
||||
const factorAuth = function(cb){
|
||||
req.params.auth = r.auth
|
||||
req.params.ke = r.ke
|
||||
if(r.details.factorAuth === "1"){
|
||||
if(!r.details.acceptedMachines||!(r.details.acceptedMachines instanceof Object)){
|
||||
r.details.acceptedMachines={}
|
||||
}
|
||||
if(!r.details.acceptedMachines[req.body.machineID]){
|
||||
req.complete=function(){
|
||||
s.factorAuth[r.ke][r.uid].function = req.body.function
|
||||
s.factorAuth[r.ke][r.uid].info = response
|
||||
clearTimeout(s.factorAuth[r.ke][r.uid].expireAuth)
|
||||
s.factorAuth[r.ke][r.uid].expireAuth = setTimeout(function(){
|
||||
s.deleteFactorAuth(r)
|
||||
},1000*60*15)
|
||||
renderPage(config.renderPaths.factorAuth,{$user:{
|
||||
ke:r.ke,
|
||||
uid:r.uid,
|
||||
mail:r.mail
|
||||
},lang:r.lang})
|
||||
}
|
||||
if(!s.factorAuth[r.ke]){s.factorAuth[r.ke]={}}
|
||||
if(!s.factorAuth[r.ke][r.uid]){
|
||||
s.factorAuth[r.ke][r.uid]={key:s.nid(),user:r}
|
||||
s.onTwoFactorAuthCodeNotificationExtensions.forEach(function(extender){
|
||||
extender(r)
|
||||
auth_token: user.auth,
|
||||
ke: user.ke,
|
||||
uid: user.uid,
|
||||
mail: user.mail,
|
||||
details: user.details
|
||||
})
|
||||
req.complete()
|
||||
}else{
|
||||
req.complete()
|
||||
return failedAuthentication(req.body.function,req.body.mail,alternateLoginResponse.msg)
|
||||
}
|
||||
}else{
|
||||
checkRoute(r)
|
||||
}
|
||||
}else{
|
||||
checkRoute(r)
|
||||
}
|
||||
}
|
||||
if(r.details.sub){
|
||||
}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.sub){
|
||||
const adminUserCheckResponse = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "details",
|
||||
table: "Users",
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
['ke','=',user.ke],
|
||||
['details','NOT LIKE','%"sub"%'],
|
||||
],
|
||||
},function(err,rr) {
|
||||
if(rr && rr[0]){
|
||||
rr=rr[0];
|
||||
rr.details = JSON.parse(rr.details);
|
||||
r.details.mon_groups = rr.details.mon_groups;
|
||||
response.details = JSON.stringify(r.details);
|
||||
factorAuth()
|
||||
limit: 1,
|
||||
})
|
||||
if(adminUserCheckResponse.rows && adminUserCheckResponse.rows[0]){
|
||||
const adminUser = adminUserCheckResponse.rows[0];
|
||||
const adminUserDetails = s.parseJSON(adminUser.details);
|
||||
user.details.mon_groups = adminUserDetails.mon_groups;
|
||||
}else{
|
||||
failedAuthentication(req.body.function)
|
||||
return failedAuthentication(req.body.function,req.body.mail)
|
||||
}
|
||||
}
|
||||
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
|
||||
},
|
||||
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{
|
||||
factorAuth()
|
||||
failedAuthentication(req.body.function,req.body.mail)
|
||||
}
|
||||
}else{
|
||||
failedAuthentication(req.body.function)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(LdapAuth&&req.body.function==='ldap'&&req.body.key!==''){
|
||||
s.knexQuery({
|
||||
|
@ -500,7 +505,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(!user.uid){
|
||||
user.uid=s.gid()
|
||||
}
|
||||
response = {
|
||||
const userInfo = {
|
||||
ke:req.body.key,
|
||||
uid:user.uid,
|
||||
auth:s.createHash(s.gid()),
|
||||
|
@ -526,143 +531,89 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(rr&&rr[0]){
|
||||
//already registered
|
||||
rr = rr[0]
|
||||
response = rr;
|
||||
userInfo = rr;
|
||||
rr.details = JSON.parse(rr.details)
|
||||
response.lang = s.getLanguageFile(rr.details.lang)
|
||||
userInfo.lang = s.getLanguageFile(rr.details.lang)
|
||||
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User Authenticated'],msg:{user:user,shinobiUID:rr.uid}})
|
||||
s.knexQuery({
|
||||
action: "update",
|
||||
table: "Users",
|
||||
update: {
|
||||
auth: response.auth
|
||||
auth: userInfo.auth
|
||||
},
|
||||
where: [
|
||||
['ke','=',response.ke],
|
||||
['ke','=',userInfo.ke],
|
||||
['uid','=',rr.uid],
|
||||
]
|
||||
})
|
||||
}else{
|
||||
//new ldap login
|
||||
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User is New'],msg:{info:r.lang['Creating New Account'],user:user}})
|
||||
response.lang = r.lang
|
||||
userInfo.lang = r.lang
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Users",
|
||||
insert: response,
|
||||
insert: userInfo,
|
||||
})
|
||||
}
|
||||
response.details = JSON.stringify(response.details)
|
||||
response.auth_token = response.auth
|
||||
response.ok = true
|
||||
checkRoute(response)
|
||||
userInfo.details = JSON.stringify(userInfo.details)
|
||||
userInfo.auth_token = userInfo.auth
|
||||
userInfo.ok = true
|
||||
checkRoute(req.body.function,userInfo)
|
||||
})
|
||||
return
|
||||
}
|
||||
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Failed'],msg:{err:err}})
|
||||
//no user
|
||||
req.default()
|
||||
regularLogin()
|
||||
});
|
||||
|
||||
req.auth.close(function(err) {
|
||||
|
||||
})
|
||||
}else{
|
||||
req.default()
|
||||
regularLogin()
|
||||
}
|
||||
}else{
|
||||
req.default()
|
||||
regularLogin()
|
||||
}
|
||||
})
|
||||
}else if(req.body.function === 'super'){
|
||||
const superLoginResponse = await superLogin(req.body.mail,req.body.pass);
|
||||
if(superLoginResponse.ok){
|
||||
renderPage(config.renderPaths.super,{
|
||||
config: config,
|
||||
lang: lang,
|
||||
$user: superLoginResponse.user,
|
||||
customAutoLoad: s.customAutoLoadTree,
|
||||
currentVersion: s.currentVersion,
|
||||
})
|
||||
}else{
|
||||
if(req.body.function === 'super'){
|
||||
if(!fs.existsSync(s.location.super)){
|
||||
res.end(lang.superAdminText)
|
||||
return
|
||||
}
|
||||
var ok = s.superAuth({
|
||||
mail: req.body.mail,
|
||||
pass: req.body.pass,
|
||||
users: true,
|
||||
md5: true
|
||||
},function(data){
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Logs",
|
||||
where: [
|
||||
['ke','=','$'],
|
||||
],
|
||||
orderBy: ['time','desc'],
|
||||
limit: 30
|
||||
},(err,r) => {
|
||||
if(!r){
|
||||
r=[]
|
||||
}
|
||||
data.Logs = r
|
||||
data.customAutoLoad = s.customAutoLoadTree
|
||||
data.currentVersion = s.currentVersion
|
||||
fs.readFile(s.location.config,'utf8',function(err,file){
|
||||
data.plainConfig = JSON.parse(file)
|
||||
renderPage(config.renderPaths.super,data)
|
||||
})
|
||||
})
|
||||
})
|
||||
if(ok === false){
|
||||
failedAuthentication(req.body.function)
|
||||
failedAuthentication(req.body.function,req.body.mail)
|
||||
}
|
||||
}else{
|
||||
req.default()
|
||||
regularLogin()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(req.body.machineID&&req.body.factorAuthKey){
|
||||
if(s.factorAuth[req.body.ke]&&s.factorAuth[req.body.ke][req.body.id]&&s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
|
||||
if(s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
|
||||
if(req.body.remember==="1"){
|
||||
req.details=JSON.parse(s.factorAuth[req.body.ke][req.body.id].info.details)
|
||||
req.lang=s.getLanguageFile(req.details.lang)
|
||||
if(!req.details.acceptedMachines||!(req.details.acceptedMachines instanceof Object)){
|
||||
req.details.acceptedMachines={}
|
||||
}
|
||||
if(!req.details.acceptedMachines[req.body.machineID]){
|
||||
req.details.acceptedMachines[req.body.machineID]={}
|
||||
s.knexQuery({
|
||||
action: "update",
|
||||
table: "Users",
|
||||
update: {
|
||||
details: s.prettyPrint(req.details)
|
||||
},
|
||||
where: [
|
||||
['ke','=',req.body.ke],
|
||||
['uid','=',req.body.id],
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
req.body.function = s.factorAuth[req.body.ke][req.body.id].function
|
||||
response = s.factorAuth[req.body.ke][req.body.id].info
|
||||
response.lang = req.lang || s.getLanguageFile(JSON.parse(s.factorAuth[req.body.ke][req.body.id].info.details).lang)
|
||||
checkRoute(s.factorAuth[req.body.ke][req.body.id].info)
|
||||
clearTimeout(s.factorAuth[req.body.ke][req.body.id].expireAuth)
|
||||
s.deleteFactorAuth({
|
||||
}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,
|
||||
uid: req.body.id,
|
||||
id: req.body.id,
|
||||
machineID: req.body.machineID,
|
||||
factorAuthKey: req.body.factorAuthKey,
|
||||
})
|
||||
if(twoFactorVerificationResponse.ok){
|
||||
checkRoute(twoFactorVerificationResponse.pageTarget,twoFactorVerificationResponse.info)
|
||||
}else{
|
||||
var info = s.factorAuth[req.body.ke][req.body.id].info
|
||||
renderPage(config.renderPaths.factorAuth,{$user:{
|
||||
ke: info.ke,
|
||||
id: info.uid,
|
||||
mail: info.mail,
|
||||
},lang:req.lang});
|
||||
res.end();
|
||||
failedAuthentication(lang['2-Factor Authentication'],factorAuthObject.info.mail)
|
||||
}
|
||||
}else{
|
||||
failedAuthentication(lang['2-Factor Authentication'])
|
||||
}
|
||||
}else{
|
||||
failedAuthentication(lang['2-Factor Authentication'])
|
||||
}
|
||||
failedAuthentication(lang['2-Factor Authentication'],req.body.mail)
|
||||
}
|
||||
})
|
||||
/**
|
||||
|
@ -1794,6 +1745,76 @@ module.exports = function(s,config,lang,app,io){
|
|||
},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) {
|
||||
|
|
|
@ -2,7 +2,6 @@ var fs = require('fs');
|
|||
var os = require('os');
|
||||
var moment = require('moment')
|
||||
var request = require('request')
|
||||
var jsonfile = require("jsonfile")
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var execSync = require('child_process').execSync;
|
||||
|
@ -17,24 +16,23 @@ module.exports = function(s,config,lang,app){
|
|||
app.all([config.webPaths.superApiPrefix+':auth/logs'], function (req,res){
|
||||
req.ret={ok:false};
|
||||
s.superAuth(req.params,function(resp){
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,req.params.id)
|
||||
s.getDatabaseRows({
|
||||
monitorRestrictions: monitorRestrictions,
|
||||
table: 'Logs',
|
||||
groupKey: req.params.ke,
|
||||
groupKey: '$',
|
||||
date: req.query.date,
|
||||
startDate: req.query.start,
|
||||
endDate: req.query.end,
|
||||
startOperator: req.query.startOperator,
|
||||
endOperator: req.query.endOperator,
|
||||
limit: req.query.limit,
|
||||
archived: req.query.archived,
|
||||
endIsStartTo: true
|
||||
limit: req.query.limit || 30,
|
||||
},(response) => {
|
||||
response.rows.forEach(function(v,n){
|
||||
r[n].info = JSON.parse(v.info)
|
||||
response.rows[n].info = JSON.parse(v.info)
|
||||
})
|
||||
s.closeJsonResponse(res,{
|
||||
ok: response.ok,
|
||||
logs: response.rows
|
||||
})
|
||||
s.closeJsonResponse(res,r)
|
||||
})
|
||||
},res,req)
|
||||
})
|
||||
|
@ -102,9 +100,21 @@ module.exports = function(s,config,lang,app){
|
|||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Get Configuration (conf.json)
|
||||
*/
|
||||
app.get(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
|
||||
s.superAuth(req.params,async (resp) => {
|
||||
var endData = {
|
||||
ok: true,
|
||||
config: JSON.parse(fs.readFileSync(s.location.config)),
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Superuser : Modify Configuration (conf.json)
|
||||
*/
|
||||
app.all(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
|
||||
app.post(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
|
||||
s.superAuth(req.params,async (resp) => {
|
||||
var endData = {
|
||||
ok : true
|
||||
|
@ -117,7 +127,7 @@ module.exports = function(s,config,lang,app){
|
|||
s.systemLog('conf.json Modified',{
|
||||
by: resp.$user.mail,
|
||||
ip: resp.ip,
|
||||
old:jsonfile.readFileSync(s.location.config)
|
||||
old: s.parseJSON(fs.readFileSync(s.location.config),{})
|
||||
})
|
||||
const configError = await modifyConfiguration(postBody)
|
||||
if(configError)s.systemLog(configError)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-- --------------------------------------------------------
|
||||
-- Host: 192.168.1.31
|
||||
-- Server version: 10.1.30-MariaDB-0ubuntu0.17.10.1 - Ubuntu 17.10
|
||||
-- Host: 172.16.100.238
|
||||
-- Server version: 10.3.25-MariaDB-0ubuntu0.20.04.1 - Ubuntu 20.04
|
||||
-- Server OS: debian-linux-gnu
|
||||
-- HeidiSQL Version: 9.4.0.5125
|
||||
-- HeidiSQL Version: 11.0.0.5919
|
||||
-- --------------------------------------------------------
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
|
@ -20,13 +20,27 @@ USE `ccio`;
|
|||
CREATE TABLE IF NOT EXISTS `API` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`uid` varchar(50) DEFAULT NULL,
|
||||
`ip` tinytext,
|
||||
`ip` tinytext DEFAULT NULL,
|
||||
`code` varchar(100) DEFAULT NULL,
|
||||
`details` text,
|
||||
`time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
`details` text DEFAULT NULL,
|
||||
`time` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Cloud Timelapse Frames
|
||||
CREATE TABLE IF NOT EXISTS `Cloud Timelapse Frames` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`href` text NOT NULL,
|
||||
`details` longtext DEFAULT NULL,
|
||||
`filename` varchar(50) NOT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`size` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Cloud Videos
|
||||
CREATE TABLE IF NOT EXISTS `Cloud Videos` (
|
||||
`mid` varchar(50) NOT NULL,
|
||||
|
@ -35,59 +49,144 @@ CREATE TABLE IF NOT EXISTS `Cloud Videos` (
|
|||
`size` float DEFAULT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`end` timestamp NULL DEFAULT NULL,
|
||||
`status` int(1) DEFAULT '0' COMMENT '0:Complete,1:Read,2:Archive',
|
||||
`details` text
|
||||
`status` int(1) DEFAULT 0 COMMENT '0:Complete,1:Read,2:Archive',
|
||||
`details` text DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Events
|
||||
CREATE TABLE IF NOT EXISTS `Events` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`details` text,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
`details` text DEFAULT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
KEY `events_index` (`ke`,`mid`,`time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Events Counts
|
||||
CREATE TABLE IF NOT EXISTS `Events Counts` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`end` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`count` int(10) NOT NULL DEFAULT 1,
|
||||
`tag` varchar(30) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Files
|
||||
CREATE TABLE IF NOT EXISTS `Files` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`name` tinytext NOT NULL,
|
||||
`size` float NOT NULL DEFAULT 0,
|
||||
`details` text NOT NULL,
|
||||
`status` int(1) NOT NULL DEFAULT 0,
|
||||
`time` timestamp NOT NULL DEFAULT current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.LoginTokens
|
||||
CREATE TABLE IF NOT EXISTS `LoginTokens` (
|
||||
`loginId` varchar(255) DEFAULT '',
|
||||
`type` varchar(25) DEFAULT '',
|
||||
`ke` varchar(50) DEFAULT '',
|
||||
`uid` varchar(50) DEFAULT '',
|
||||
`name` varchar(50) DEFAULT 'Unknown',
|
||||
`lastLogin` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
UNIQUE KEY `logintokens_loginid_unique` (`loginId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Logs
|
||||
CREATE TABLE IF NOT EXISTS `Logs` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`info` text,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
`info` text DEFAULT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
KEY `logs_index` (`ke`,`mid`,`time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Monitors
|
||||
CREATE TABLE IF NOT EXISTS `Monitors` (
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` varchar(50) DEFAULT NULL,
|
||||
`shto` text,
|
||||
`shfr` text,
|
||||
`details` longtext,
|
||||
`shto` text DEFAULT NULL,
|
||||
`shfr` text DEFAULT NULL,
|
||||
`details` longtext DEFAULT NULL,
|
||||
`type` varchar(50) DEFAULT 'jpeg',
|
||||
`ext` varchar(50) DEFAULT 'webm',
|
||||
`protocol` varchar(50) DEFAULT 'http',
|
||||
`host` varchar(100) DEFAULT '0.0.0.0',
|
||||
`path` varchar(100) DEFAULT '/',
|
||||
`port` int(8) DEFAULT '80',
|
||||
`fps` int(8) DEFAULT '1',
|
||||
`port` int(8) DEFAULT 80,
|
||||
`fps` int(8) DEFAULT 1,
|
||||
`mode` varchar(15) DEFAULT NULL,
|
||||
`width` int(11) DEFAULT '640',
|
||||
`height` int(11) DEFAULT '360'
|
||||
`width` int(11) DEFAULT 640,
|
||||
`height` int(11) DEFAULT 360,
|
||||
KEY `monitors_index` (`ke`,`mode`,`type`,`ext`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Presets
|
||||
CREATE TABLE IF NOT EXISTS `Presets` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` text,
|
||||
`details` text,
|
||||
`type` enum('monitor','event','user') DEFAULT NULL
|
||||
`name` text DEFAULT NULL,
|
||||
`details` text DEFAULT NULL,
|
||||
`type` varchar(50) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Schedules
|
||||
CREATE TABLE IF NOT EXISTS `Schedules` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` text DEFAULT NULL,
|
||||
`details` text DEFAULT NULL,
|
||||
`start` varchar(10) DEFAULT NULL,
|
||||
`end` varchar(10) DEFAULT NULL,
|
||||
`enabled` int(1) NOT NULL DEFAULT 1
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Timelapse Frames
|
||||
CREATE TABLE IF NOT EXISTS `Timelapse Frames` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext DEFAULT NULL,
|
||||
`filename` varchar(50) NOT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`size` int(11) NOT NULL,
|
||||
KEY `timelapseframes_index` (`ke`,`mid`,`time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Timelapses
|
||||
CREATE TABLE IF NOT EXISTS `Timelapses` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext DEFAULT NULL,
|
||||
`date` date NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
`end` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
`size` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Users
|
||||
CREATE TABLE IF NOT EXISTS `Users` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
|
@ -95,12 +194,13 @@ CREATE TABLE IF NOT EXISTS `Users` (
|
|||
`auth` varchar(50) DEFAULT NULL,
|
||||
`mail` varchar(100) DEFAULT NULL,
|
||||
`pass` varchar(100) DEFAULT NULL,
|
||||
`accountType` int(1) DEFAULT '0',
|
||||
`details` longtext,
|
||||
`accountType` int(1) DEFAULT 0,
|
||||
`details` longtext DEFAULT NULL,
|
||||
UNIQUE KEY `mail` (`mail`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table ccio.Videos
|
||||
CREATE TABLE IF NOT EXISTS `Videos` (
|
||||
`mid` varchar(50) DEFAULT NULL,
|
||||
|
@ -111,69 +211,14 @@ CREATE TABLE IF NOT EXISTS `Videos` (
|
|||
`size` float DEFAULT NULL,
|
||||
`frames` int(11) DEFAULT NULL,
|
||||
`end` timestamp NULL DEFAULT NULL,
|
||||
`status` int(1) DEFAULT '0',
|
||||
`archived` int(1) DEFAULT '0',
|
||||
`details` text
|
||||
`status` int(1) DEFAULT 0,
|
||||
`archived` int(1) DEFAULT 0,
|
||||
`details` text DEFAULT NULL,
|
||||
KEY `videos_index` (`time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Files
|
||||
CREATE TABLE IF NOT EXISTS `Files` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`name` tinytext NOT NULL,
|
||||
`size` float NOT NULL DEFAULT '0',
|
||||
`details` text NOT NULL,
|
||||
`status` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
-- Dumping structure for table ccio.Schedules
|
||||
CREATE TABLE IF NOT EXISTS `Schedules` (
|
||||
`ke` varchar(50) DEFAULT NULL,
|
||||
`name` text,
|
||||
`details` text,
|
||||
`start` varchar(10) DEFAULT NULL,
|
||||
`end` varchar(10) DEFAULT NULL,
|
||||
`enabled` int(1) NOT NULL DEFAULT '1'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Dumping structure for table ccio.Timelapses
|
||||
CREATE TABLE IF NOT EXISTS `Timelapses` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext,
|
||||
`date` date NOT NULL,
|
||||
`time` timestamp NOT NULL,
|
||||
`end` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`size` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Dumping structure for table ccio.Timelapse Frames
|
||||
CREATE TABLE IF NOT EXISTS `Timelapse Frames` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext,
|
||||
`filename` varchar(50) NOT NULL,
|
||||
`time` timestamp NULL DEFAULT NULL,
|
||||
`size` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
-- Dumping structure for table ccio.Timelapse Frames
|
||||
CREATE TABLE IF NOT EXISTS `Cloud Timelapse Frames` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`href` text NOT NULL,`details` longtext,`filename` varchar(50) NOT NULL,`time` timestamp NULL DEFAULT NULL,`size` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Dumping structure for table ccio.Events Counts
|
||||
CREATE TABLE IF NOT EXISTS `Events Counts` (
|
||||
`ke` varchar(50) NOT NULL,
|
||||
`mid` varchar(50) NOT NULL,
|
||||
`details` longtext NOT NULL,
|
||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`end` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`count` int(10) NOT NULL DEFAULT 1,
|
||||
`tag` varchar(30) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
$(document).ready(function(){
|
||||
var alternateLoginsBox = $('#alternate-logins')
|
||||
function getAlternateLogins(){
|
||||
$.get(getApiPrefix('loginTokens'),function(data){
|
||||
var rows = data.rows
|
||||
alternateLoginsBox.empty()
|
||||
if(rows.length > 0){
|
||||
$.each(rows,function(n,row){
|
||||
alternateLoginsBox.append(`<div class="row" login-id="${row.loginId}">
|
||||
<div class="col-md-4" style="text-transform:capitalize;font-size: 150%;">
|
||||
<i class="fa fa-${row.type}"></i>
|
||||
<span>${row.type}</span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>${row.name}</div>
|
||||
<div title="${lang.lastLogin}">${moment(row.lastLogin).format('YYYY-MM-DD hh:mm:ss A')}</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<a class="btn btn-sm btn-danger unlink-account"><i class="fa fa-unlink"></i> ${lang.Unlink}</a>
|
||||
</div>
|
||||
</div>`)
|
||||
})
|
||||
}else{
|
||||
alternateLoginsBox.append(`<div class="row">
|
||||
<div class="col-md-12 text-center epic-text" style="margin: 0">
|
||||
${lang.noLoginTokensAdded}
|
||||
</div>
|
||||
</div>`)
|
||||
}
|
||||
})
|
||||
}
|
||||
getAlternateLogins()
|
||||
alternateLoginsBox.on('click','.unlink-account',function(){
|
||||
var loginId = $(this).parents('[login-id]').attr('login-id')
|
||||
$.confirm.create({
|
||||
title: lang['Unlink Login'],
|
||||
body: lang.noUndoForAction,
|
||||
clickOptions: {
|
||||
title: lang['Unlink'],
|
||||
class: 'btn-danger'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.get(getApiPrefix('loginTokens') + '/' + loginId + '/delete',function(data){
|
||||
if(data.ok){
|
||||
new PNotify({
|
||||
title: lang.Unlinked,
|
||||
text: lang.loginHandleUnbound,
|
||||
type: 'success'
|
||||
})
|
||||
alternateLoginsBox.find(`[login-id="${loginId}"]`).remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
window.drawAlternateLoginsToSettings = getAlternateLogins
|
||||
})
|
|
@ -31,6 +31,8 @@ $(document).ready(function(){
|
|||
$.confirm.body.css('word-wrap','break-word')
|
||||
}
|
||||
$.confirm.body.html(options.body)
|
||||
}else{
|
||||
alert('No Title, Language file Update?')
|
||||
}
|
||||
if(options.clickOptions && options.clickCallback)$.confirm.click(options.clickOptions,options.clickCallback)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
$(document).ready(function(){
|
||||
$('#settings').on('click','.google-sign-in',function(){
|
||||
var signInWindow = window.open(getApiPrefix('loginTokenAddGoogle'),'popup','width=300,height=300,scrollbars=no,resizable=no');
|
||||
if(!signInWindow || signInWindow.closed || typeof signInWindow.closed=='undefined'){
|
||||
alert(`Your Popup Blocker is disabling this feature.`)
|
||||
}else{
|
||||
signInWindow.onbeforeunload = function(){
|
||||
drawAlternateLoginsToSettings()
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
})
|
|
@ -267,8 +267,11 @@ $(document).ready(function(){
|
|||
schema: schema
|
||||
});
|
||||
|
||||
configurationEditor.setValue(shinobiConfig);
|
||||
|
||||
function loadConfiguationIntoEditor(){
|
||||
$.get(superApiPrefix + $user.sessionKey + '/system/configure',function(data){
|
||||
configurationEditor.setValue(data.config);
|
||||
})
|
||||
}
|
||||
// configurationEditor.on("change", function() {
|
||||
// // Do something...
|
||||
// });
|
||||
|
@ -307,5 +310,12 @@ $(document).ready(function(){
|
|||
submitConfiguration()
|
||||
return false;
|
||||
})
|
||||
$.ccio.ws.on('f',function(d){
|
||||
switch(d.f){
|
||||
case'init_success':
|
||||
loadConfiguationIntoEditor()
|
||||
break;
|
||||
}
|
||||
})
|
||||
window.configurationEditor = configurationEditor
|
||||
})
|
||||
|
|
|
@ -224,7 +224,7 @@ $.aN.f.submit(function(e){
|
|||
//client side email check
|
||||
$.aN.e.on('change','[name="mail"]',function(){
|
||||
var thisVal = $(this).val()
|
||||
$.each(users,function(n,user){
|
||||
$.each(loadedUsers,function(n,user){
|
||||
if($.aN.selected && user.ke !== $.aN.selected.ke && thisVal.toLowerCase() === user.mail.toLowerCase()){
|
||||
new PNotify({text:lang['Email address is in use.'],type:'error'})
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ $.aN.e.on('change','[name="mail"]',function(){
|
|||
//client side group key check
|
||||
$.aN.e.on('change','[name="ke"]',function(){
|
||||
var thisVal = $(this).val()
|
||||
$.each(users,function(n,user){
|
||||
$.each(loadedUsers,function(n,user){
|
||||
if(!$.aN.modeIsEdit() && user.ke === thisVal){
|
||||
new PNotify({text:lang['Group Key is in use.'] + ' ' + lang['Create Sub-Accounts at /admin'],type:'error'})
|
||||
}
|
||||
|
|
|
@ -269,3 +269,4 @@
|
|||
</div>
|
||||
</div>
|
||||
<script src="<%-window.libURL%>libs/js/dash2.usersettings.js"></script>
|
||||
<script src="<%-window.libURL%>libs/js/dash2.alternateLogins.js"></script>
|
||||
|
|
|
@ -202,6 +202,7 @@ var mediaRecorder;
|
|||
var chunks = [];
|
||||
var count = 0;
|
||||
|
||||
function initVideoStream(){
|
||||
$.ccio.vid = {element:$('#video')[0],canvas:$('#canvas')[0],data:$('#data')};
|
||||
$.ccio.vid.element.controls = false;
|
||||
navigator.getUserMedia(constraints,function(stream,fn) {
|
||||
|
@ -209,7 +210,7 @@ navigator.getUserMedia(constraints,function(stream,fn) {
|
|||
$.ccio.vid.element.srcObject = stream;
|
||||
$('[record]').click($.ccio.startSending)
|
||||
////////
|
||||
if($user.mons.length>0&&$.ls.getItem('Shinobi_Dashcam')){
|
||||
if(loadedMonitors.length>0&&$.ls.getItem('Shinobi_Dashcam')){
|
||||
$('[monitor="'+$.ls.getItem('Shinobi_Dashcam')+'"]').click()
|
||||
if($.ls.getItem('Shinobi_Dashcam_Started') === 1){
|
||||
setTimeout(function(){
|
||||
|
@ -220,32 +221,40 @@ navigator.getUserMedia(constraints,function(stream,fn) {
|
|||
$.ccio.selected = null;
|
||||
}
|
||||
}, function(err){console.error('getUserMedia',err)});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//draw selectable mons
|
||||
var loadedMonitors = []
|
||||
function drawMonitorList(callback){
|
||||
var tmp='';
|
||||
if($user.mons&&$user.mons.length>0){
|
||||
$('#monitors').empty()
|
||||
$.get($user.auth_token + '/monitor/' + $user.ke,function(monitors){
|
||||
loadedMonitors = [];
|
||||
if(monitors && monitors.length > 0){
|
||||
$.ccio.mon={};
|
||||
$.each($user.mons,function(n,v){
|
||||
$.each(monitors,function(n,v){
|
||||
if(v.type === 'dashcam'){
|
||||
loadedMonitors.push(v)
|
||||
v.details = JSON.parse(v.details);
|
||||
$.ccio.mon[v.mid] = v;
|
||||
tmp += '<a class="list-group-item" monitor="'+v.mid+'">'+v.name+'</a>';
|
||||
}
|
||||
})
|
||||
$('#monitors').html(tmp)
|
||||
}else{
|
||||
tmp+="<h2>No Streamer Monitors Setup</h2>"
|
||||
tmp+="<h2>No Dashcam Monitors Setup</h2>"
|
||||
tmp+="<small>Login to the Dashboard and add one. Set it to record or watch only.</small>"
|
||||
$('#msg').html(tmp)
|
||||
}
|
||||
delete(tmp);
|
||||
callback(monitors)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
drawMonitorList(function(){
|
||||
initVideoStream()
|
||||
})
|
||||
})
|
||||
|
||||
$('body').on('click','[monitor]',function(e){
|
||||
e.e=$(this);e.a=e.e.attr('monitor'),e.m=$.ccio.mon[e.a];
|
||||
$.ccio.selected=e.a;$('#selected').html(e.a);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%
|
||||
var details = JSON.parse($user.details)
|
||||
var details = $user.details
|
||||
if(!details.sub){ %>
|
||||
<script>
|
||||
window.getAdminApiPrefix = function(piece){
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<% include blocks/header %>
|
||||
<link rel="stylesheet" href="<%-window.libURL%>libs/themes/Ice/style.css">
|
||||
<meta name="google-signin-client_id" content="<%- config.appIdGoogleSignIn %>">
|
||||
<style>
|
||||
.wide-text{
|
||||
text-transform: uppercase;
|
||||
|
@ -67,7 +68,7 @@
|
|||
<form id="login-form" method="post" style="display: block;margin:0">
|
||||
<input type="hidden" name="machineID" id="machineID" value="">
|
||||
<% var message,timeLeft;if(message){ %>
|
||||
<div class="form-group text-center monospace">
|
||||
<div class="form-group text-center monospace" id ="login-message">
|
||||
<%= message %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
@ -80,6 +81,13 @@
|
|||
<div class="form-group f_i_input f_i_ldap" style="display:none">
|
||||
<input name="key" id="key" tabindex="2" class="monospace form-control wide-text" placeholder="Group Key">
|
||||
</div>
|
||||
<div class="form-group" style="display:none">
|
||||
<select class="form-control wide-text" name="alternateLogin">
|
||||
<option value="" selected>Default</option>
|
||||
<option value="google">Google</option>
|
||||
</select>
|
||||
<input style="display:none" name="alternateLoginToken" class="monospace form-control wide-text" placeholder="Group Key">
|
||||
</div>
|
||||
<% if(config.showLoginSelector === true){ %>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
|
@ -123,6 +131,11 @@
|
|||
<div class="form-group">
|
||||
<button type="submit" name="login-submit" id="login-submit" tabindex="4" class="btn btn-success btn-block wide-text" style="color:#FFF"><i class="fa fa-key"></i> <%- lang.Login %></button>
|
||||
</div>
|
||||
<% if(config.allowGoogleSignOn){ %>
|
||||
<div class="form-group text-center">
|
||||
<div class="g-signin2" data-onsuccess="onGoogleSignIn" style="display: inline-block;"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="form-group text-center" style="margin:0">
|
||||
<span style="<%- config.poweredByShinobiClass %>;margin-right: 10px" class="epic-text text-green"><i class="fa fa-sign-in"></i> <%- lang['Remember Me'] %></span>
|
||||
<div class="text-right" title="<%- lang['Remember Me'] %>" style="display:inline-block">
|
||||
|
@ -149,6 +162,7 @@
|
|||
</div>
|
||||
<script src="<%-window.libURL%>libs/js/material.min.js"></script>
|
||||
<script>
|
||||
var googleSignIn = false;
|
||||
<% var failedLogin;if(failedLogin===true){ %>
|
||||
localStorage.removeItem('ShinobiLogin_'+location.host)
|
||||
<% } %>
|
||||
|
@ -167,11 +181,13 @@
|
|||
$('#machineID').val($.ccio.auth)
|
||||
})
|
||||
$.ccio.f.submit(function(e){
|
||||
$('#login-message').remove()
|
||||
$('input').css('border-color','')
|
||||
e.e=$(this),e.s=e.e.serializeObject(),e.inputs=e.e.find('input,button');
|
||||
if(e.s.remember){
|
||||
localStorage.setItem('ShinobiLogin_'+location.host,JSON.stringify({mail:e.s.mail,pass:e.s.pass,function:e.s.function}))
|
||||
}else{localStorage.removeItem('ShinobiLogin_'+location.host)}
|
||||
if(googleSignIn)googleSignOut()
|
||||
})
|
||||
if($.ccio.ls){
|
||||
$.ccio.ls=JSON.parse($.ccio.ls);
|
||||
|
@ -212,3 +228,23 @@ $('[selector]').change(function(e){
|
|||
$('.'+e.a+'_text').text($(this).find('option:selected').text())
|
||||
}).change();
|
||||
</script>
|
||||
<% if(config.allowGoogleSignOn){ %>
|
||||
<script src="https://apis.google.com/js/platform.js" async defer></script>
|
||||
<script>
|
||||
function onGoogleSignIn(googleUser) {
|
||||
var id_token = googleUser.getAuthResponse().id_token;
|
||||
$.ccio.f.find('[name="mail"],[name="pass"],.g-signin2').hide()
|
||||
$.ccio.f.find('[name="alternateLogin"]').val('google')
|
||||
$.ccio.f.find('[name="alternateLoginToken"]').val(id_token)
|
||||
$.ccio.f.find('[name="login-submit"]').html(`<i class="fa fa-google"></i> <%- lang.Login %>`)
|
||||
googleSignIn = true
|
||||
$.ccio.f.submit()
|
||||
}
|
||||
function googleSignOut() {
|
||||
var auth2 = gapi.auth2.getAuthInstance();
|
||||
auth2.signOut().then(function () {
|
||||
console.log('Google Signed out.');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<% } %>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<%
|
||||
window.libURL = originalURL + global.s.checkCorrectPathEnding(config.webPaths.home)
|
||||
%>
|
||||
<meta name="google-signin-client_id" content="<%- config.appIdGoogleSignIn %>">
|
||||
<script src="<%-window.libURL%>libs/js/jquery.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background: #333;
|
||||
}
|
||||
.g-signin2 {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
<div class="g-signin2" data-onsuccess="onGoogleSignIn" style="display: inline-block;"></div>
|
||||
<script src="https://apis.google.com/js/platform.js" async defer></script>
|
||||
<script>
|
||||
function onGoogleSignIn(googleUser) {
|
||||
console.log('Logged in to Google! Binding...')
|
||||
var id_token = googleUser.getAuthResponse().id_token;
|
||||
$.post(location.href,{
|
||||
loginToken: id_token,
|
||||
},function(data){
|
||||
googleSignOut()
|
||||
if(data.ok){
|
||||
window.close()
|
||||
}else{
|
||||
console.log(data)
|
||||
$('.g-signin2').html(data.msg || 'Failed to Save').css({
|
||||
color: "#fff",
|
||||
textAlign: "center",
|
||||
fontFamily: "monospace",
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function googleSignOut() {
|
||||
var auth2 = gapi.auth2.getAuthInstance();
|
||||
auth2.signOut().then(function () {
|
||||
console.log('Google Signed out.');
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -74,12 +74,11 @@ $.ccio.start=function(){
|
|||
|
||||
|
||||
|
||||
|
||||
function initVideoStream(){
|
||||
$.ccio.vid = {e:$('#video'),c:$('#canvas')};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia||navigator.mediaDevices.getUserMedia;
|
||||
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
|
||||
navigator.getUserMedia({video: true},function(stream,fn) {
|
||||
//set video element
|
||||
if ($.ccio.vid.e[0].mozSrcObject !== undefined) {
|
||||
$.ccio.vid.e[0].mozSrcObject = stream;
|
||||
} else {
|
||||
|
@ -92,24 +91,23 @@ $.ccio.vid = {e:$('#video'),c:$('#canvas')};
|
|||
$('[record]').click($.ccio.start)
|
||||
})
|
||||
}, function(err){console.error('getUserMedia',err)});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//draw selectable mons
|
||||
var loadedMonitors = []
|
||||
function drawMonitorList(callback){
|
||||
var tmp='';
|
||||
if($user.mons&&$user.mons.length>0){
|
||||
$('#monitors').empty()
|
||||
$.get($user.auth_token + '/monitor/' + $user.ke,function(monitors){
|
||||
loadedMonitors = [];
|
||||
if(monitors && monitors.length > 0){
|
||||
$.ccio.mon={};
|
||||
$.each($user.mons,function(n,v){
|
||||
$.each(monitors,function(n,v){
|
||||
if(v.type === 'streamer'){
|
||||
loadedMonitors.push(v)
|
||||
v.details = JSON.parse(v.details);
|
||||
$.ccio.mon[v.mid] = v;
|
||||
tmp += '<a class="list-group-item" monitor="'+v.mid+'">'+v.name+'</a>';
|
||||
}
|
||||
})
|
||||
$('#monitors').html(tmp)
|
||||
}else{
|
||||
|
@ -117,7 +115,20 @@ $.ccio.vid = {e:$('#video'),c:$('#canvas')};
|
|||
tmp+="<small>Login to the Dashboard and add one. Set it to record or watch only.</small>"
|
||||
$('#msg').html(tmp)
|
||||
}
|
||||
delete(tmp);
|
||||
callback(monitors)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
drawMonitorList(function(){
|
||||
initVideoStream()
|
||||
if(loadedMonitors.length > 0 && $.ls.getItem('Shinobi_socket_camera')){
|
||||
$('[monitor="'+$.ls.getItem('Shinobi_socket_camera')+'"]').click()
|
||||
}else{
|
||||
$.ccio.selected=null;
|
||||
}
|
||||
})
|
||||
})
|
||||
$('body').on('click','[monitor]',function(e){
|
||||
e.e=$(this);e.a=e.e.attr('monitor'),e.m=$.ccio.mon[e.a];
|
||||
$.ccio.selected=e.a;$('#selected').html(e.a);
|
||||
|
@ -127,7 +138,6 @@ $.ccio.vid = {e:$('#video'),c:$('#canvas')};
|
|||
})
|
||||
|
||||
|
||||
if($user.mons.length>0&&$.ls.getItem('Shinobi_socket_camera')){$('[monitor="'+$.ls.getItem('Shinobi_socket_camera')+'"]').click()}else{$.ccio.selected=null;}
|
||||
|
||||
$('body')
|
||||
.on('click','.logout',function(e){
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
</style>
|
||||
<script>
|
||||
var superApiPrefix = location.search === '?p2p=1' ? location.pathname + '/' : "<%=originalURL%><%=config.webPaths.superApiPrefix%>"
|
||||
var shinobiConfig = <%- JSON.stringify(plainConfig) %>
|
||||
</script>
|
||||
<% customAutoLoad.superLibsCss.forEach(function(lib){ %>
|
||||
<link rel="stylesheet" href="<%-window.libURL%>libs/css/<%-lib%>">
|
||||
|
@ -171,6 +170,31 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
var loadedUsers = {}
|
||||
function drawUserList(){
|
||||
loadedUsers = {}
|
||||
$('#accounts table').empty()
|
||||
$.get(superApiPrefix + $user.sessionKey + '/accounts/list/admin',function(data){
|
||||
$.each(data.users,function(n,v){
|
||||
loadedUsers[v.ke] = v
|
||||
$.ccio.tm(0,v,'#accounts table')
|
||||
})
|
||||
})
|
||||
}
|
||||
function drawSystemLogs(){
|
||||
$('#logs table').empty()
|
||||
$.get(superApiPrefix + $user.sessionKey + '/logs',function(data){
|
||||
if(data.ok){
|
||||
$.each(data.logs.reverse(),function(n,v){
|
||||
$.ccio.tm(4,v,'#logs table')
|
||||
})
|
||||
}else{
|
||||
var html = `<div class="mt-3 mb-4"><p><i class="fa fa-4x fa-exclamation-circle text-danger"></i></p><h3>Database is not running or unreachable</h3><p>Please ensure it is started then restart Shinobi.</p><p>By default Shinobi uses <b>MariaDB</b>, an SQL server, as the database engine.</p><p>If you need to install the database files again you may follow <a target="_blank" href="https://shinobi.video/articles/2019-02-22-how-to-install-the-shinobi-database-manually">this article.</a></p></div>`
|
||||
$('#main-card').html(html)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$.ccio={accounts:{}};$.ls=localStorage;
|
||||
if(!$user.lang||$user.lang==''){
|
||||
$user.lang="<%-config.language%>"
|
||||
|
@ -202,6 +226,8 @@ $.ccio.ws.on('f',function(d){
|
|||
switch(d.f){
|
||||
case'init_success':
|
||||
$user.sessionKey = d.superSessionKey
|
||||
drawUserList()
|
||||
drawSystemLogs()
|
||||
break;
|
||||
case'log':
|
||||
$.ccio.tm(4,d.log,'#logs table')
|
||||
|
@ -244,7 +270,7 @@ $.ccio.tm=function(x,d,z,k){
|
|||
break;
|
||||
case 4://log row, draw to global and monitor
|
||||
if(!d.time){d.time=$.ccio.init('t')}
|
||||
tmp+='<tr class="search-row"><td><span title="'+d.time+'" class="livestamp"></span><br><small>'+d.time+'</small><br><small>'+d.mid+'</small></td><td></td><td><pre class="pre-inline">'+$.ccio.init('jsontoblock',JSON.parse(d.info))+'</pre></td></tr>'
|
||||
tmp+='<tr class="search-row"><td><span title="'+d.time+'" class="livestamp"></span><br><small>'+d.time+'</small><br><small>'+d.mid+'</small></td><td></td><td><pre class="pre-inline">'+$.ccio.init('jsontoblock',d.info)+'</pre></td></tr>'
|
||||
break;
|
||||
}
|
||||
if(z){
|
||||
|
@ -290,25 +316,6 @@ $.ccio.init=function(x,d,z,k){
|
|||
//logs
|
||||
$.logs={e:$('#logs')}
|
||||
////
|
||||
$(document).ready(function(){
|
||||
<% if(!users || !(users instanceof Array)){ %>
|
||||
var html = `<div class="mt-3 mb-4"><p><i class="fa fa-4x fa-exclamation-circle text-danger"></i></p><h3>Database is not running or unreachable</h3><p>Please ensure it is started then restart Shinobi.</p><p>By default Shinobi uses <b>MariaDB</b>, an SQL server, as the database engine.</p><p>If you need to install the database files again you may follow <a target="_blank" href="https://shinobi.video/articles/2019-02-22-how-to-install-the-shinobi-database-manually">this article.</a></p></div>`
|
||||
$('#main-card').html(html)
|
||||
<% }else{ %>
|
||||
users = <%- JSON.stringify(users) %>;
|
||||
if(users){
|
||||
$.each(users,function(n,v){
|
||||
$.ccio.tm(0,v,'#accounts table')
|
||||
})
|
||||
}else{
|
||||
|
||||
}
|
||||
<% } %>
|
||||
$.each(<%-JSON.stringify(Logs)%>,function(n,v){
|
||||
$.ccio.tm(4,v,'#logs table')
|
||||
})
|
||||
})
|
||||
//
|
||||
$('body')
|
||||
.on('click','.logout',function(e){
|
||||
localStorage.removeItem('ShinobiLogin_'+location.host);location.href='/';
|
||||
|
|
Loading…
Reference in New Issue