diff --git a/camera.js b/camera.js index 053fe33d..5fbb17fe 100644 --- a/camera.js +++ b/camera.js @@ -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 diff --git a/definitions/en_CA.js b/definitions/en_CA.js index 54155364..2c079300 100644 --- a/definitions/en_CA.js +++ b/definitions/en_CA.js @@ -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", diff --git a/languages/en_CA.json b/languages/en_CA.json index dcb5a49b..5e0717ff 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -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", diff --git a/libs/auth.js b/libs/auth.js index 1dbf7038..c70355f7 100644 --- a/libs/auth.js +++ b/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 + 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() } }) } diff --git a/libs/auth/alternateLogins.js b/libs/auth/alternateLogins.js new file mode 100644 index 00000000..3c4f3ce5 --- /dev/null +++ b/libs/auth/alternateLogins.js @@ -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, + } +} diff --git a/libs/auth/google.js b/libs/auth/google.js new file mode 100644 index 00000000..b92e77a0 --- /dev/null +++ b/libs/auth/google.js @@ -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": ` ${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, + } +} diff --git a/libs/auth/logins.js b/libs/auth/logins.js new file mode 100644 index 00000000..8d56af62 --- /dev/null +++ b/libs/auth/logins.js @@ -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) + } +} diff --git a/libs/auth/utils.js b/libs/auth/utils.js new file mode 100644 index 00000000..29035a8a --- /dev/null +++ b/libs/auth/utils.js @@ -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, + } +} diff --git a/libs/sql.js b/libs/sql.js index a26cb902..b2361b4d 100644 --- a/libs/sql.js +++ b/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) } } diff --git a/libs/webServerPaths.js b/libs/webServerPaths.js index 123eebdc..228196cf 100644 --- a/libs/webServerPaths.js +++ b/libs/webServerPaths.js @@ -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,169 +295,157 @@ 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), - customAutoLoad: s.customAutoLoadTree - }) + renderPage(config.renderPaths.dashcam,{ + // config: s.getConfigWithBranding(req.hostname), + $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), - customAutoLoad: s.customAutoLoadTree - }) + renderPage(config.renderPaths.streamer,{ + // config: s.getConfigWithBranding(req.hostname), + $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), - addStorage:s.dir.addStorage, - fs:fs, - __dirname:s.mainDirectory, + 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: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(){ + 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: "select", - columns: "*", + action: "update", table: "Users", + update: { + auth: sessionKey + }, 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()); - s.knexQuery({ - action: "update", - table: "Users", - update: { - auth: r.auth - }, - where: [ - ['ke','=',r.ke], - ['uid','=',r.uid], - ] - }) - response = { - 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) - }) - req.complete() - }else{ - req.complete() - } - }else{ - checkRoute(r) - } - }else{ - checkRoute(r) - } - } - if(r.details.sub){ - s.knexQuery({ - action: "select", - columns: "details", - table: "Users", - where: [ - ['ke','=',r.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() - }else{ - failedAuthentication(req.body.function) - } - }) - }else{ - factorAuth() - } - }else{ - failedAuthentication(req.body.function) - } + ['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.sub){ + const adminUserCheckResponse = await s.knexQueryPromise({ + action: "select", + columns: "details", + table: "Users", + where: [ + ['ke','=',user.ke], + ['details','NOT LIKE','%"sub"%'], + ], + 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{ + 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{ + failedAuthentication(req.body.function,req.body.mail) + } } 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'){ - 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) - }) - }) + }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, }) - if(ok === false){ - failedAuthentication(req.body.function) - } }else{ - req.default() + 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{ - 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({ - ke: req.body.ke, - uid: req.body.id, - }) - }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(); - } - }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) { diff --git a/libs/webServerSuperPaths.js b/libs/webServerSuperPaths.js index e6e5cd5a..32a94abb 100644 --- a/libs/webServerSuperPaths.js +++ b/libs/webServerSuperPaths.js @@ -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) diff --git a/sql/framework.sql b/sql/framework.sql index f3f116c1..8bdecd65 100644 --- a/sql/framework.sql +++ b/sql/framework.sql @@ -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 */; diff --git a/web/libs/js/dash2.alternateLogins.js b/web/libs/js/dash2.alternateLogins.js new file mode 100644 index 00000000..c0d6df1b --- /dev/null +++ b/web/libs/js/dash2.alternateLogins.js @@ -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(`