UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

817 lines (731 loc) 27.1 kB
console.time('Server on '+JOE.webconfig.port); var express = require('express'); var https = require('https'); var fs = require('fs'); var compress = require('compression'); var serverPath = '../'; var server = express(); var http = require('http').Server(server); var https = require('https'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var basicAuth = require('basic-auth'); var path = require('path'); var pem = require('pem'); var os = require('os'); const $J = require('./UniversalShorthand'); var rd = require('renderizer')({templatesDir:path.resolve(JOE.joedir,JOE.webconfig.templatesDir)}); function coloredLog(message) { console.log(JOE.Utils.color('[server]', 'module'), message); } var authBool = JOE.isAuthorized = function(req,res){ var users = (JOE.Data && JOE.Data.user) || []; if(req.cookies._j_user && req.cookies._j_token){ var User = users.where({name:req.cookies._j_user,token:req.cookies._j_token})[0]||false; if(User){ req.User = User; return true; } } var user = basicAuth(req); var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress; if (!user || !user.name || !user.pass) { return false; }; //console.log('a user',user); var User = users.where({name:user.name,password:user.pass})[0]||false; //console.log(User); if(User || !users.length){ req.User = User||{}; res.cookie('_j_user',User.name); res.cookie('_j_token',User.token); return true; } else { return false; } } var auth = JOE.auth = function (req, res, next) { function unauthorized(res) { if(JOE.webconfig.authorization && JOE.webconfig.authorization.url && !req.query.noSSO){ var state = req.originalUrl; let url = JOE.webconfig.authorization.url+'&state='+state; res.redirect(url); return; } res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); return res.send(401); }; var authorized = authBool(req,res); if(authorized){ return next(); }else{ return unauthorized(res); } }; function stringFunctions(propObject){ for(var p in propObject){ if(typeof propObject[p] == 'function'){ //propObject[p] = '(' + propObject[p].toString() + ')'; propObject[p] = '(' + propObject[p].toString().replace(/\\n/g,'\\n') + ')'; }else if(typeof propObject[p] == "object"){ stringFunctions(propObject[p]); } } return propObject; } server.use(cookieParser()); server.use( bodyParser.json({limit: '50mb'}) ); // to support JSON-encoded bodies // to support URL-encoded bodies server.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); server.use(compress()); server.get(JOE.webconfig.joepath+'server/*',function(req,res,next){ res.send('unauthorized'); }); server.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); server.use(JOE.webconfig.joepath,express.static(JOE.joedir)); //USER server.get(['/API/user/:method'],auth,function(req,res,next){ var users = (JOE.Data && JOE.Data.user) || []; var method = req.params.method; var User; if(!req.User){ /* if(req.cookies._j_user && req.cookies._j_token){ User = users.where({name:req.cookies._j_user,token:req.cookies._j_token})[0]||false; } if (!User){ var user = basicAuth(req); if (user.name && user.pass) { User = users.where({name: user.name, password: user.pass})[0] || false; } }*/ res.jsonp({error:'user or method not found'}); return {error:'user or method not found'}; }else{ User = req.User; } if (User) { switch (method) { case 'current': //get current user from creds var payload = $c.merge({}, User); delete payload.password; res.jsonp({user: payload}); return payload; break; case 'logout': logit('logging out user '+req.User.name); req.User = null; res.jsonp({logout:true}); return next(); break; case 'apps': logit('getting apps for '+req.User.name); userApps = []; if (req.User.apps && req.User.apps.length) { req.User.apps.map(function (app) { if (JOE.Apps.cache[app]) { userApps.push({ name: app, title: JOE.Apps.cache[app].title, description: JOE.Apps.cache[app].description }); } }); } res.jsonp({userApps}); return;//return next(); break; } } }); //SEARCH server.get(['/API/search/','/API/search/:query'],auth,function(req,res,next){ var bm = new Benchmarker(); var query = req.params.query || req.query.query || {}; var limit = req.query.limit || 0; var queryObj = (typeof query == "object")?query:eval('('+query+')'); var payload = {query:queryObj}; payload.results = JOE.Cache.search(queryObj); /*payload.results.map(function(res){ res = $c.copyObject(res); if(res.password || res.token){ delete res.password; delete res.token; } })*/ payload.count = payload.results.length; if(req.query.limit){ payload.results = payload.results.slice(0,limit) } payload.results = cleanPayload(payload.results); payload.benchmark = bm.stop(); res.jsonp(payload); }); function cleanPayload(results){ results = JSON.parse(JSON.stringify(results)); results.map(r=>{ if(r.password){ r.password = null; } }) return results; } //ITEM QUERY - secured, preferred server.get(['/API/item/:collection/fields/:fields','/API/item/:collection/:key/:value','/API/item/:collection'],auth,function(req,res,next){ var st = new Date().getTime(); var collection = req.params.collection || req.query.collection; var key = req.params.key || req.query.key; var value = req.params.value || req.query.value; var fields = req.params.fields || req.query.fields; var filter = {}; if(key && value){ filter[key] = value; } var payload = {collection:collection,filter:filter}; payload.item = (JOE.Data[collection] ||[]).where(filter); if(key && value){ payload.item = payload.item[0] || false; } else if(fields){ // fields = fields.split(','); // var finalItems = []; // payload.item.map(i=>{ // let copy = {}; // fields.map(f=>{ // if(f){ // copy[f]=i[f]; // } // }) // finalItems.push(copy); // }) payload.item = JOE.Utils.ProjectedItemsFromFields(fields,payload.item); } payload.item = cleanPayload(payload.item); payload.elapsed = new Date().getTime() - st; res.jsonp(payload); }); //OBJECT SINGLE ITEM QUERY server.get(['/API/object/:collection/:key/:value', '/API/object/:collection/:key/:value/field/:field', '/API/object/:collection/:key/:value/fields/:fields' ],auth,function(req,res,next){ var collection = req.params.collection || req.query.collection; var key = req.params.key || req.query.key; var value = req.params.value || req.query.value; var field = req.params.field || req.query.field; var fields = req.params.fields || req.query.fields; var filter = {}; if(key && value){ filter[key] = value; } var object = (JOE.Data[collection] ||[]).where(filter)[0] || false; if(!object){ res.jsonp({error:'object not found'}) return; } if(field){ var pl = {}; pl[field] = object[field]; res.jsonp(pl); return; } if(fields){ fields = fields.split(','); var pl = {}; fields.map(f=>{ if(f){ pl[f]=object[f]; } }) res.jsonp(pl); return; } res.jsonp(object); }); //HISTORY server.get(['/API/history/:key/:itemid'],auth,function(req,res,next){ var st = new Date().getTime(); var elapsed; var key = req.params.key; var id = req.params.itemid || req.query.itemid; var quick = req.params.quick || false; var fields = req.params.fields || req.query.fields; var query = {}; query[key] = id; if(!id){ res.jsonp({error:'no itemid specified'}); return; } JOE.Storage.load('_history',query,function(err,data){ if(err){ res.jsonp({error:err}); return; } if(!fields){ elapsed = new Date().getTime() - st; res.jsonp({query:query,results:data,elapsed}) }else{ var finalItems = JOE.Utils.ProjectedItemsFromFields(fields,data); elapsed = new Date().getTime() - st; res.jsonp({query:query,results:finalItems,elapsed}) } }) }); //COMMENTS server.get(['/API/comments/:mode/:id'],auth,function(req,res,next){ var mode = req.params.mode; var id = req.params.id; var query = false; var payload={error:'comments request syntax'}; switch(mode){ case 'user': query = {'user._id':id}; break; case 'item': query = {'item._id':id}; break; } if(query){ JOE.Storage.load('comments',query,function(err,data){ if(err){ res.jsonp({error:err}); return; } res.jsonp({ query:query, length:data.length, comments:data }); }) }else{ res.jsonp(payload); } }); //CACHE server.get(['/API/cache/update','/API/cache/update/:collections'],auth,function(req,res,next){ var collections = (req.params.collections||'').split(','); try{ JOE.Cache.update(function(){ console.log('cache updated manually'); res.jsonp({event:'cache updated'}); }) }catch(e){ res.jsonp({error:e}); } }); //DATASET server.get(['/API/dataset/:names','/API/datasets/:names'],auth,function(req,res,next){ var uBM = new Benchmarker(); var collections = (req.params.names||'').split(','); var payload = { datasets:{}, counts:{total:0} }; /*queryness*/ var query = req.query.query; if(query){ var queryObj = (typeof query == "object")?query:eval('('+query+')'); var payload = {query:queryObj}; } var permitted = []; var isSuper = false; var isNew = false; if(!$c.isEmpty(req.User)){ isSuper = (req.User.role == "super"); permitted = req.User.schemas || []; }else if(!JOE.Data.user || !JOE.Data.user.length){ isNew = true; } collections.map(function(c){ if(c){ if(isSuper || isNew || permitted.indexOf(c) != -1){ payload.datasets[c] = JOE.Data[c] || []; }else{payload.datasets[c] = [];} payload.counts[c] = payload.datasets[c].length; payload.counts.total+= payload.datasets[c].length; } }); payload.benchmark = uBM.stop(); res.jsonp(payload); }); //SCHEMA server.get(['/API/schema/:names','/API/schemas/:names'],auth,function(req,res,next){ var sBM = new Benchmarker(); var schemaname = (req.params.names||'').split(','); var format = req.query.format; if(!schemaname || !schemaname.length){ res.jsonp({error:'no schema sent'}); } var payload = {}; var fields = {}; //'./server/'; var schemapath,fieldspath; var permitted = []; var isSuper = false; var isNew = false; if(!$c.isEmpty(req.User)){ isSuper = (req.User.role == "super"); permitted = JSON.parse(JSON.stringify(req.User.schemas)) || []; if(req.User.items && req.User.items.length && req.query.mode=="additionalItems"){ JOE.Cache.search({_id:{$in:req.User.items}}).map(i=>{ permitted.push(i.itemtype); }) } permitted = Array.from(new Set(permitted)); }else if(!JOE.Data || !JOE.Data.user || !JOE.Data.user.length){ isNew = true; } schemaname.map(function(s){ if(isSuper || isNew || permitted.indexOf(s) != -1){ payload[s] = JOE.Schemas.schema[s]; } }); var F = (req.query.fields||req.params.fields||'core').split(','); F.map(function(f){ try { fieldspath = serverPath+'fields/' + f + '.js'; delete require.cache[require.resolve(fieldspath)]; fields = require(fieldspath); stringFunctions(fields); JOE.Fields[f]=fields; }catch(e){ fields ={error:'field '+f+' not found'}; } }); var bm = sBM.stop(); switch(format){ case 'js': res.set('Content-Type', 'application/javascript'); var js_payload =JSON.stringify({schemas:payload,fields:fields,benchmark:bm},null, ' '); res.send('var joe_info = '+js_payload); break; default: res.jsonp({schemas:payload,fields:fields,benchmark:bm}); break; } }); //PLUGINS async function pluginHandling(req,res,next){ var pBM = new Benchmarker(); var plugin = req.params.plugin; var data = $c.merge(req.query,req.body) || {}; //var app = req.params.app; var method = req.params.method || 'default'; var errors = []; // if(!plugin || !app){ // errors.push('plugin or app name not supplied'); // } // var app_obj = JOE.Apps.cache[app]; // if(!app_obj){ // errors.push('app "'+app+'" not found'); // } // if(!app_obj.plugins || !app_obj.plugins.contains(plugin)){ // errors.push('plugin "'+plugin+'" not found'); // } var pluginClass = JOE.Apps.plugins[plugin]; var protected; if(!pluginClass){ errors.push('plugin "'+plugin+'" not initialized') } else if(method == "protected" || !pluginClass[method] || (pluginClass.protected && pluginClass.protected.indexOf(method) != -1)){ errors.push('method '+method+' not found'); } if(errors.length){ res.jsonp({errors:errors,failedat:'Server'}); console.log('[plugin] errors:'); console.log(errors); return; } try{ if(typeof pluginClass[method] == "string"){ var response = pluginClass[method]; }else{ if(pluginClass.async && pluginClass.async[method]){ var response = await pluginClass[method](data,req,res); }else{ var response = pluginClass[method](data,req,res); } } if(response && response.use_callback){ return; } if(response && response.unauthorized){ res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); return res.send(401); } if(response && (response.errors || response.error)){ var errstr = (response.errors || response.error); logit(errstr) res.jsonp(response); return; } if(typeof response == "string"){ res.send(response); }else{ if(response && response['content-type']){ try{ console.log(response); res.set('Content-Type', response['content-type']+';charset=utf-8'); res.set('Content-Disposition', 'attachment; filename="'+response.filename+'"'); res.send(response.content); }catch(e){ console.log('error',e); res.jsonp({error:e}); } }else{ res.jsonp(response); } } }catch(e){ console.log(e); res.jsonp({errors:e}); } logit(JOE.Utils.color('[plugins]','plugin')+' ran '+plugin+' > '+method+' in '+pBM.stop()+' secs'); } server.get(['/API/plugin/:plugin','/API/plugin/:plugin/:method'],pluginHandling); server.post(['/API/plugin/:plugin','/API/plugin/:plugin/:method'],pluginHandling); //SAVE server.get(['/API/save/','/API/save/:itemid'],auth,function(req,res,next){ var object,prev_object,message = ''; if(req.params.itemid){ prev_object = $J.get(req.params.itemid); if(!prev_object){ res.jsonp({status:'error',error:"object not found"}); return; } object = Object.assign({},prev_object,req.query); message = `${object.itemtype||'item'} updated`; }else{ object = req.query; } object.name = decodeURI(object.name); for(prop in object){ if(typeof object[prop] == "string" && ["true","false"].indexOf(object[prop].toLowerCase()) != -1){ object[prop] = $c.parseBoolean(object[prop]); } } object.joeUpdated = new Date(); //TODO: check to make sure schema idprop is present JOE.Storage.save(object,object.itemtype,function(err,results){ //var object = merge({},results); var itemtype = results.itemtype; var _id = results._id; /* JOE.Storage.load(itemtype,{'_id':_id},function(err,data){ if(err){ res.jsonp({status:'error',error:err,results:data}); }else{ res.jsonp({status:'success',message:'',error:err,results:data}); JOE.io.emit('item_updated',{results:data}) } }) */ JOE.Cache.update(function(){ var updated = $J.get(_id); JOE.Storage.load(itemtype,{'_id':_id},function(err,data){ if(err){ res.jsonp({status:'error',error:err}); }else{ res.jsonp({status:'success',message:'',error:err,results:updated}); JOE.io.emit('item_updated',{results:updated}) } }) },[object.itemtype]) },{user:req.User}); }); /* //UPDATE server.get('/API/update/:itemid',auth,function(req,res,next){ var object = $J.get(req.params.itemid); res.jsonp({status:'test',object:object}); if(item.) //var object = req.query; return; //TODO: check to make sure schema idprop is present JOE.Storage.save(object,object.itemtype,function(err,results){ //var object = merge({},results); var itemtype = results.itemtype; var _id = results._id; JOE.Storage.load(itemtype,{'_id':_id},function(err,data){ if(err){ res.jsonp({status:'error',error:err,results:data}); }else{ res.jsonp({status:'success',error:err,results:data}); io.emit('item_updated',{results:data}) } }); },{user:req.User}); }); */ server.get('/',function(req,res,next){ logit('blank request'); if(JOE.Data.site.where({url:''})[0]){ JOE.Sites.parseRoute(req,res,next); }else{ next(); } }) server.get(['/'],function(req,res){ fs.readFile(JOE.joedir+'/pages/joe.html', 'utf8', function (err,data) { var payload={ webconfig:JOE.webconfig } res.send(fillTemplate(data,payload)); }) }) //JOE APP server.get(['/JOE/','/JOE/:appname'],auth,function(req,res,next){ var nonAppExtensions = ['jpeg','jpg','gif','ico','pdf','png','tif','tiff']; var appname = req.params.appname||'joe'; var ext = appname.substr(appname.lastIndexOf('.')+1); if(nonAppExtensions.indexOf(ext) != -1){ res.status(404).send('file "'+appname+'"['+ext+'] not found.'); return; } var agent = req.headers["user-agent"]; var templatePage = JOE.joedir+'/pages/template.html'; if(agent.indexOf('Trident') != -1 || agent.indexOf('MSIE') != -1|| agent.indexOf('Edge') != -1){ templatePage = JOE.joedir+'/pages/template_ie.html'; } //console.log(agent); fs.readFile(templatePage, 'utf8', function (err,data) { if (err) { res.json({error:err}); return console.log(err); } try{ var apps = []; //var appspath = serverPath+'Apps.js'; //delete require.cache[require.resolve(appspath)]; //JOE.Apps = require(appspath);//JOE.webconfig.apps; var Apps = JOE.Apps.cache; // if(!JOE[appname]){ // res.send({'error':'no app found: '+appname}); // //return; // } var currentApp = Apps[appname]; if(!currentApp){ res.send({'error':'app not found'}) return; } var currentAppCollections = JOE.Utils.propAsFuncOrValue(currentApp.collections); if(!$c.isEmpty(req.User)){ if(req.User.apps && req.User.apps.indexOf(appname) == -1){ if(req.User.role !="super"){ res.send({'error':'invalid permission, talk to your admin'}); return; } } }else if(JOE.Data.user && JOE.Data.user.length){ res.send({'error':'invalid permission, try refreshing'}); return; } for(var app in Apps){ apps.push(app); } var sites = (JOE.Data.site || []).sortBy('url'); var plugins = []; for(var pl in JOE.Apps.plugins){ plugins.push(pl); } var settings ={}; var data_settings = JOE.Data.setting||[]; for(var s = 0, tot = data_settings.length; s <tot;s++){ settings[data_settings[s].name]= data_settings[s].value; } var USER = { }; if(!$c.isEmpty(req.User)){ USER.name = req.User.name; USER.schemas = (req.User.schemas||[]).join(','); USER.role = req.User.role; USER.apps = req.User.apps; USER.items = (req.User.items||[]).join(','); USER.styles =req.User.styles; } //var collections = []; let uappsObj = []; USER.apps.map(a=>{ let aa = Apps[a]||{}; uappsObj.push({name:a,title:aa.title,description:aa.description}); }); var payload = { APPNAME:currentApp.title || appname, APPINFO:JSON.stringify(stringFunctions(currentApp)), APPS:apps.join(','), USERAPPS:(USER.apps||apps||[]).join(','), USERAPPSOBJ:JSON.stringify(uappsObj), webconfig:JOE.webconfig, settings:settings, JOEPATH:JOE.webconfig.joepath, COLLECTIONS:currentAppCollections.join(), ALL_COLLECTIONS:JOE.Apps.collections.join(), DEFAULT_SCHEMAS:JOE.webconfig.default_schemas.join(','), PLUGINS:plugins.join(), USER:USER, STARTDATE:JOE.STARTDATE.toISOString(), //SCHEMALIST:JOE.Schemas.schemaList.join(','), //ALL_COLLECTIONS:Apps['joe'].collections.join(), SITES:sites, STATS:{ memory:{ current:process.memoryUsage(), freemem:os.freemem(), totalmem:os.totalmem() } } }; //logit(console.log('memory',Math.round((process.memoryUsage().rss/1024/1024*100)/100))); res.send(fillTemplate(data,payload)); }catch(e){ res.json({error:e}); console.log(e); return console.log('error loading app '+appname+': '+e); } }); }); server.get(['/RENDER/:contentType/:templateName'],function(req,res){ var contentType = req.params.contentType; var templateName = req.params.templateName; if(!Renderizer.TEMPLATES[contentType] || !Renderizer.TEMPLATES[contentType][templateName]){ res.status(404).send({error:`${contentType} '${templateName}' not found`}) return; } var params = Object.assign(req.params,req.query); res.send(Renderizer.TEMPLATES[contentType][templateName](params,req)); }); JOE.webDir = webDir = JOE.appDir+'/'+JOE.webconfig.webDir+'/'; JOE.Utils.setupFileFolder(webDir,'web'); coloredLog('listening on '+JOE.webconfig.port); server.use('/',express.static(JOE.webDir)); http.listen(JOE.webconfig.port,function(){ //console.log('joe listening on '+JOE.webconfig.port); coloredLog('using webDir: '+JOE.webDir); }); JOE.httpServer = http; var httpsPort = JOE.webconfig.httpsPort; if(httpsPort){ var httpsServer = https.Server({key: JOE.Pem.serviceKey, cert: JOE.Pem.certificate}, server); httpsServer.listen(httpsPort); coloredLog(' https on '+httpsPort); JOE.httpsServer = httpsServer; // pem.createCertificate({selfSigned:true}, function(err, keys){ // if(err){ // console.log(err); // } // https.Server({key: keys.serviceKey, cert: keys.certificate}, server).listen(httpsPort); // console.log(JOE.Utils.color('[server]','module')+' https on '+httpsPort); // JOE.httpsServer = https; // }); } module.exports = server;