diff --git a/app.js b/app.js index ea52023..4b8d547 100644 --- a/app.js +++ b/app.js @@ -8,17 +8,15 @@ // GDS: Global Data System global['gds'] = { debug: (process.env.NODE_ENV === 'debug') ? true : false, - db: null, cache: {}, cfg: require(__dirname+'/bin/config') }; -global['modules'] = {}; global['__dirname'] = __dirname; /** * load modules */ -let load = (name) => { +let load = global['requireModule'] = (name) => { return require(__dirname+'/bin/'+name+'/module'); }; @@ -33,16 +31,9 @@ env_vars.forEach((el) => { }); if(env_missing) process.exit(); -global['modules'].events = load('events'); // event handler +load('events'); // event handler -global['modules'].logs = load('logs'); // log handler -global['logs'] = global['modules'].logs; // alias +global['logs'] = load('logs'); // log handler -global['modules'].database = load('database'); // database service -global['modules'].web = load('web'); // web server -global['modules'].auth = load('auth'); // authentication handler - -global['logs'] = global['modules'].logs; // alias - -// start web server -global['modules'].web.start(); +let web = load('web'); // web server +web.start(); // start web server diff --git a/bin/database/module.js b/bin/database/module.js index c144c80..bf718a7 100644 --- a/bin/database/module.js +++ b/bin/database/module.js @@ -13,28 +13,40 @@ var sanitize = require('mongo-sanitize'); var crypto = require('crypto'); var methods = {}; -// connect -mongoose.connect(global['gds'].cfg.mongoose.uri, { - useNewUrlParser: true -}); -mongoose.set('useFindAndModify', false); - -global['gds'].db = mongoose.connection; +var log = require(global['__dirname']+'/bin/logs/module'); +var cfg = require(global['__dirname']+'/bin/config'); +var db; var mdls = require('./models.js'); -var models = mdls(global['gds'].db); +var models; -(async function() { - global['gds'].db = await global['gds'].db.useDb(global['gds'].cfg.mongoose.db); - models = mdls(global['gds'].db); -})(); +/** + * connects to db + * @author Ruben Meyer + * @async + */ +methods.connect = async () => { + if(typeof db !== "undefined") return; -// connection error handling -global['gds'].db.on('error', (data) => { - global['modules'].logs.error('MongoDB connection error:\n', data); - process.exit(); // exit on connection error -}); + // connect + mongoose.connect(cfg.mongoose.uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + useFindAndModify: false + }); + + + db = mongoose.connection; + db = await db.useDb(cfg.mongoose.db); + models = mdls(db); + + // connection error handling + db.on('error', (data) => { + log.error('MongoDB connection error:\n', data); + process.exit(); // exit on connection error + }); +} // // // //////// //////// /////// // // // // // // // @@ -47,19 +59,19 @@ global['gds'].db.on('error', (data) => { /** * Adds User to Database - * @author Ruben Meyer - * @param {String} nick nickname - * @param {String} email email - * @param {String} passhash hashed password - * @param {Number} group Group id (normally 0 -> user) - * @param {Function} callback Callback function (error, reply) + * @author Ruben Meyer + * @async + * @param {String} nick nickname + * @param {String} email email + * @param {String} passhash hashed password + * @param {Number} group Group id (normally 0 -> user) + * @return {Array} async (reply, err) */ -methods.addUser = (nick, email, passhash, group, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof nick !== 'string') return callback(new TypeError('nick is not a string::database.addUser('+nick+','+email+','+passhash+','+group+',callback)', module.filename)); - if(typeof email !== 'string') return callback(new TypeError('email is not a string::database.addUser('+nick+','+email+','+passhash+','+group+',callback)', module.filename)); - if(typeof passhash !== 'string') return callback(new TypeError('passhash is not a string::database.addUser('+nick+','+email+','+passhash+','+group+',callback)', module.filename)); - if(isNaN(group)) return callback(new TypeError('group is not a number::database.addUser('+nick+','+email+','+passhash+','+group+',callback)', module.filename)); +methods.addUser = async (nick, email, passhash, group) => { + if(typeof nick !== 'string') return {err: new TypeError('nick is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)}; + if(typeof email !== 'string') return {err: new TypeError('email is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)}; + if(typeof passhash !== 'string') return {err: new TypeError('passhash is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)}; + if(isNaN(group)) return {err: new TypeError('group is not a number::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)}; let userModel = models.user; @@ -69,70 +81,70 @@ methods.addUser = (nick, email, passhash, group, callback) => { user.passhash = sanitize(passhash); user.group = sanitize(group); - user.save((err) => { - if(!err) callback(null, 1); - else callback(err); - }); + try { + reply = await user.save(); + return {reply: 1}; + } catch(err) { + return {err: err}; + } }; /** - * Deletes User from Database + * deletes user identified by haystack from database * @author Ruben Meyer - * @TODO + * @async + * @TODO add functionality * @param {String} haystack email or nick - * @param {Function} callback Callback function (error, reply) + * @return {Array} async(reply, err) */ -methods.delUser = (haystack, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof haystack !== 'string') return callback(new TypeError('haystack is not a string::database.delUser('+haystack+',callback)', module.filename)); +methods.delUser = async (haystack) => { + if(typeof haystack !== 'string') return {err: new TypeError('haystack is not a string::database.delUser('+haystack+')', module.filename)}; let userModel = models.user; // sanitize input haystack = sanitize(haystack); - userModel.findOneAndDelete().or([{nickname: haystack}, {email: haystack}]) - .then((rep) => { - // TODO delete user - global['logs'].debug('deleted user: '+haystack); - callback(null, 1); - }).catch((err) => { - callback(err); - }); + try { + reply = await userModel.findOneAndDelete().or([{nickname: haystack}, {email: haystack}]).exec(); + log.debug('deleted user: '+haystack); + return {reply: 1}; + } catch(err) { + return {err: err}; + } }; /** * get all users * @author Ruben Meyer - * @param {Function} callback Callback function (reply -> Array users) + * @async + * @return {Array} async(reply, err) */ -methods.getUsers = (callback) => { - if(typeof callback !== 'function') callback = function() {}; - +methods.getUsers = async () => { let userModel = models.user; - userModel.find({}) - .then((users) => { + try { + users = await userModel.find({}).exec(); if(users.length > 0) - return callback(null, users); + return {reply: users}; else - return callback(null, false); - }).catch((err) => { - return callback(err); - }); + return {reply: false}; + } catch(err) { + return {err: err}; + } }; /** - * query users by email, nickname or rememberme token + * query users by UUID, email, nickname or rememberme token * @author Ruben Meyer + * @async * @param {String|String[]} haystack email or nick - * @param {Function} callback Callback function (reply -> Array users) + * @return async(reply, err) */ -methods.getUser = (haystack, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof haystack !== 'string' && typeof haystack !== 'object') return callback(new TypeError('email or nickname is not a string|object::database.getUser('+haystack+',callback)', module.filename)); +methods.getUser = async (haystack) => { + if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('email or nickname is not a string|object::database.getUser('+haystack+')', module.filename)}; let userModel = models.user; @@ -154,35 +166,39 @@ methods.getUser = (haystack, callback) => { } } - userModel.find().or(or) - .then((users) => { + try { + users = await userModel.find().or(or).exec(); + if(users.length > 0) - return callback(null, users); + return {reply: users}; else - return callback(null, false); - }).catch((err) => { - return callback(err); - }); + return {reply: false}; + } catch(err) { + return {err: err}; + } }; /** * updates obj keys in user entry * @author Ruben Meyer + * @async * @param {Number} id User ID * @param {Object} obj data - * @param {Function} callback Callback function + * @return {Array} async(reply, err) */ -methods.updateUser = (id, obj, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof id !== 'string') return callback(new TypeError('id is not a string::database.updateUser('+id+','+JSON.stringify(obj)+',callback)', module.filename)); - if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.updateUser('+id+','+JSON.stringify(obj)+',callback)', module.filename)); +methods.updateUser = async (id, obj) => { + if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)}; + if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)}; let userModel = models.user; - userModel.findByIdAndUpdate(id, obj, (err, data) => { - if(err) callback(err); - else callback(null, data); - }); + + try { + data = await userModel.findByIdAndUpdate(id, obj).exec(); + return {data: data}; + } catch(err) { + return {err: err}; + } }; @@ -190,43 +206,49 @@ methods.updateUser = (id, obj, callback) => { /** * updates data based on login * @author Ruben Meyer + * @async * @TODO UPDATE METHOD; PROBABLY OUTDATED * @param {Number} id User ID * @param {Object} data data JSON -> remember - * @param {Function} callback Callback function (date => 'Login Date', token => 'RememberMe Cookie Token') + * @return {Array} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err) */ -methods.addActivity = (id, data, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof id !== 'string') return callback(new TypeError('id is not a string::database.updateNewAction('+id+','+JSON.stringify(options)+',callback)', module.filename)); - if(typeof options !== 'object' && options !== null) return callback(new TypeError('obj is not an object::database.updateUserProfile('+id+','+JSON.stringify(obj)+',callback)', module.filename)); +methods.addActivity = async (id, data) => { + if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateNewAction('+id+','+JSON.stringify(options)+')', module.filename)}; + if(typeof options !== 'object' && options !== null) return {err: new TypeError('obj is not an object::database.updateUserProfile('+id+','+JSON.stringify(obj)+')', module.filename)}; let date = new Date().toISOString(); let timestamp = new Date(date).getTime(); - functions.updateUserProfile(id, { - last_action: date - }, (err, rep) => { - if(err) return callback(err); + try { + reply = await methods.updateUser(id, { + last_action: date + }); + if(options.rememberme && options.new_token !== false) { var token = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => (c ^ crypto.randomBytes(new Uint8Array(1).length)[0] & 15 >> c / 4).toString(16)); - var Remember = models.remember; - Remember.findOneAndUpdate({userId: id}, {token: token, timestamp: Date.now()}, {upsert: true}, (err1, data) => { - if(err1) callback(err1); - return callback(null, { + + try { + data = await Remember.findOneAndUpdate({userId: id}, {token: token, timestamp: Date.now()}, {upsert: true}).exec(); + return {reply: { date: date, timestamp: timestamp, token: token - }); - }); + }}; + } + catch(err) { + return {err: err}; + } } else { - callback(null, { + return {reply: { date: date, timestamp: timestamp, token: options.old_token - }); + }}; } - }); + } catch(err) { + return {err: err}; + } }; @@ -241,27 +263,28 @@ methods.addActivity = (id, data, callback) => { /** * get Applications * @author Ruben Meyer - * @param {Function} callback Callback function (err, apps) + * @async + * @return {Array} async(apps, err) */ -methods.getApps = (callback) => { - if(typeof callback !== 'function') callback = function() {}; +methods.getApps = async () => { var Application = models.application; - - Application.find({}, (err, apps) => { - if(err) callback(err); - else callback(null, apps); - }); + try { + apps = await Application.find({}).exec(); + return {reply: apps}; + } catch(err) { + return {err: err}; + } }; /** * return auth obj * @author Ruben Meyer + * @async * @param {Object} obj data obj (aId, uId) - * @param {Function} callback Callback function (err, obj) obj -> (aId, uId, token) + * @return {Array} async({timestamp, token}, err) */ -methods.setAuthCode = (obj, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+',callback)', module.filename)); +methods.setAuthCode = async (obj) => { + if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+')', module.filename)}; var AuthCode = models.authCode; @@ -275,74 +298,80 @@ methods.setAuthCode = (obj, callback) => { timestamp: Date.now() }; - AuthCode.findOneAndUpdate(query, change, {upsert: true}, (err1, data) => { - if(err1) callback(err1); - return callback(null, { + try { + data = await AuthCode.findOneAndUpdate(query, change, {upsert: true}).exec(); + return {reply: { timestamp: change.timestamp, token: change.token - }); - }); + }}; + } catch(err) { + return {err: err}; + } }; /** * return auth obj * @author Ruben Meyer + * @async * @param {Object} obj data obj (aId, aSecret, uId, token) - * @param {Function} callback Callback function (err, bool) + * @return {Array} async(bool, err) */ -methods.getAuth = (obj, callback) => { - if(typeof callback !== 'function') callback = function() {}; - if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+',callback)', module.filename)); +methods.getAuth = async (obj) => { + if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+')', module.filename)}; var AuthCode = models.authCode; - AuthCode.findOne({ - $and: [ - {applicationId: mongoose.Types.ObjectId(obj.aId)}, - {userId: mongoose.Types.ObjectId(obj.uId)}, - {token: obj.token} - ] - }, (err, data) => { - if(err) callback(err); - else { - if(typeof data === "object") { - if(data === null || data === []) return callback(null, false); - var Application = models.application; - Application.findOne({ + try { + data = await AuthCode.findOne({ + $and: [ + {applicationId: mongoose.Types.ObjectId(obj.aId)}, + {userId: mongoose.Types.ObjectId(obj.uId)}, + {token: obj.token} + ] + }).exec(); + + if(typeof data === "object") { + if(data === null || data === []) return {reply: false}; + var Application = models.application; + + try { + data1 = await Application.findOne({ $and: [ {_id: mongoose.Types.ObjectId(obj.aId)}, {secret: obj.aSecret} ] - }, (err1, data1) => { - if(err1) callback(err1); - else { - if(obj.token == data.token - && obj.aId == String(data.applicationId) - && obj.uId == String(data.userId) - && obj.aSecret == data1.secret) { - callback(null, true); - //functions.setAuthCode({ - // aId: obj.aId, - // uId: obj.uId - //}, () => {}); - } - else callback(null, false); - } - }); - } else callback(null, false); - } - }); + }).exec(); + + if(obj.token == data.token + && obj.aId == String(data.applicationId) + && obj.uId == String(data.userId) + && obj.aSecret == data1.secret) { + return {reply: true}; + //methods.setAuthCode({ + // aId: obj.aId, + // uId: obj.uId + //}); + } + else return{reply: false}; + } catch(err) { + return {err: err}; + } + } else return {reply: false}; + } catch(err) { + return {err: err}; + } }; /** * return if app is permitted to do access call * @author Ruben Meyer + * @async * @param {Object} obj data obj (aId, redirectUrl) - * @param {Function} callback Callback function (err, bool) + * @return {Array} async(bool, err) */ -methods.verifyAppCall = (obj, callback) => { - +methods.verifyAppCall = async (obj) => { + return {}; }; // //////// //////// //////// //////// //////// @@ -354,17 +383,20 @@ methods.verifyAppCall = (obj, callback) => { //////////////////////////////////////////////////////// /** - * callback with user count + * returns user count * @author Ruben Meyer - * @param {Function} callback Callback function (reply -> int) + * @async + * @return {Array} async(int, err) */ -methods.userCount = (callback) => { - if(typeof callback !== 'function') callback = function() {}; - +methods.userCount = async () => { let userModel = models.user; - userModel.countDocuments({}, (err, count) => { - callback((err) ? err : count); - }); + + try { + count = await userModel.countDocuments({}).exec(); + return {reply: count}; + } catch(err) { + return {err: err}; + } }; diff --git a/bin/web/module.js b/bin/web/module.js index 918ef96..6992dde 100644 --- a/bin/web/module.js +++ b/bin/web/module.js @@ -7,6 +7,8 @@ // init var methods = {}; +var cfg = require(global['__dirname']+'/bin/config'); + /** * start web server * @author Ruben Meyer @@ -33,7 +35,7 @@ methods.start = () => { // Access Control Headers app.use( (req, res, next) => { res.set({ - 'X-Powered-By': global['gds'].cfg.web.poweredBy + 'X-Powered-By': cfg.web.poweredBy }); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); @@ -50,6 +52,7 @@ methods.start = () => { // path already cached; not exist if(global['gds'].cache.web[joined_path] == false) { res.status(404).end(); + global['logs'].info("[web] (404) path not found: "+joined_path); // path already cached; exist } else if(global['gds'].cache.web[joined_path] == true){ let contentType = mime.contentType(path.extname(joined_path)); @@ -66,6 +69,7 @@ methods.start = () => { fs.createReadStream(joined_path).pipe(res); } else { res.status(404).end(); + global['logs'].info("[web] (404) path not found: "+joined_path); } }); } @@ -76,7 +80,7 @@ methods.start = () => { app.use(bp.urlencoded({ extended: true })); - app.use(cp(global['gds'].cfg.web.cookieKey)); + app.use(cp(cfg.web.cookieKey)); // Pretty print if(app.get('env') === 'debug') @@ -84,7 +88,7 @@ methods.start = () => { // Sessions session_options = { - secret: global['gds'].cfg.web.sessionKey, + secret: cfg.web.sessionKey, resave: false, saveUninitialized: false, cookie: {}}; if(app.get('env') === 'production') { @@ -93,13 +97,19 @@ methods.start = () => { app.use(session_handler(session_options)); // web routes - app.use('/', require(global['__dirname']+'/bin/web/routes/static')); - app.use('/api', require(global['__dirname']+'/bin/web/routes/api')); + (async function() { + let mRoutes = require(global['__dirname']+'/bin/web/routes/static'); + let mainRoutes = await mRoutes.getRoutes(); + app.use('/', mainRoutes); + let rAPI = require(global['__dirname']+'/bin/web/routes/api'); + let restAPI = await rAPI.getRoutes(); + app.use('/api', restAPI); - // start server - app.listen(global['gds'].cfg.web.port, () => { - global['modules'].logs.log("Server is listening on port: "+global['gds'].cfg.web.port); - }); + // start server + app.listen(cfg.web.port, () => { + global['logs'].log("Server is listening on port: "+cfg.web.port); + }); + })(); }; module.exports = methods; diff --git a/bin/web/routes/api.js b/bin/web/routes/api.js index 1fa6857..8256f7c 100644 --- a/bin/web/routes/api.js +++ b/bin/web/routes/api.js @@ -6,57 +6,64 @@ var express = require('express'); var route = express.Router(); +asyncer = require('express-async-handler'); -/** - * register a user; currently not implemented - * @url /register - * @method POST - */ -route.post('/register', (req, res) => { - // if registration is disabled - if(!global['app'].cfg.web.registration) { - return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); - } else { - // am i rite? - return res.type('json').status(200).end(JSON.stringify({})); - } -}); +getRoutes = async () => { + let db = global['requireModule']('database'); + await db.connect(); -/** - * login a user - * @url /api/login - * @method POST - * @POST ['email', 'password'] - * @TODO add new activity 'action.user.login' - */ -route.post('/login', (req, res) => { - // if user is logged in (existing session); FAIL - if(req.session.user) { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.logout.required' - })); - } - // check body variables - if(!req.body.email || !req.body.password) { - return res.type('json').status(401).end(JSON.stringify({ - status: 401, - message: [ - 'msg.request.data.missing', - 'msg.auth.login.failed' - ] - })); - } - let email = req.body.email; - let pass = req.body.password; + /** + * register a user; currently not implemented + * @url /register + * @method POST + */ + route.post('/register', (req, res) => { + // if registration is disabled + if(!global['app'].cfg.web.registration) { + return res.type('json').status(400).end(JSON.stringify({status: 400, message: "msg.auth.registration.deactivated"})); + } else { + // am i rite? + return res.type('json').status(200).end(JSON.stringify({})); + } + }); + + /** + * login a user + * @url /api/login + * @method POST + * @POST ['email', 'password'] + * @TODO add new activity 'action.user.login' + */ + route.post('/login', asyncer(async (req, res) => { + // if user is logged in (existing session); FAIL + if(req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.logout.required' + })); + } + + // check body variables + if(!req.body.email || !req.body.password) { + return res.type('json').status(401).end(JSON.stringify({ + status: 401, + message: [ + 'msg.request.data.missing', + 'msg.auth.login.failed' + ] + })); + } + let email = req.body.email; + let pass = req.body.password; + + // database query: get user by email + user = await db.getUser(email); - // database query: get user by email - global['modules'].database.getUser(email, (err, rep) => { // if database error - if(err) { + if(user.err) { // log error while debugging - global['logs'].debug(err); + global['logs'].debug(user.err); // login failed because of database error return res.type('json').status(500).end(JSON.stringify({ @@ -69,7 +76,7 @@ route.post('/login', (req, res) => { } // no reply (user does not exist) or password is wrong - if(!rep || rep === null || rep.length == 0 || rep.length > 1 || !global['modules'].auth.validateHash(rep[0].passhash, pass)) { + if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1 || !global['requireModule']('auth').validateHash(user.reply[0].passhash, pass)) { return res.type('json').status(401).end(JSON.stringify({ status: 401, message: 'msg.auth.login.failed' @@ -81,8 +88,8 @@ route.post('/login', (req, res) => { // add session data req.session.user = { - 'id': rep[0]._id, - 'group': rep[0].group + 'id': user.reply[0]._id, + 'group': user.reply[0].group }; return res.type('json').end(JSON.stringify({ @@ -91,71 +98,72 @@ route.post('/login', (req, res) => { type: 'form' // TODO: types - { form, access_app} })); } + + })); + + /** + * apps verify token + * @url /api/authenticate + * @method POST + * @POST ['applicationId', 'applicationSecret', 'userId', 'token'] + * @TODO add implementation + */ + route.post('/authenticate', (req, res) => { + // TODO: authenticate }); -}); -/** - * apps verify token - * @url /api/authenticate - * @method POST - * @POST ['applicationId', 'applicationSecret', 'userId', 'token'] - * @TODO add implementation - */ -route.post('/authenticate', (req, res) => { - // TODO: authenticate -}); + /** + * cancel app request and clear it + * @url /api/cancel + * @method GET + */ + route.get('/cancel', (req, res) => { + // if user is logged in + if(req.session && req.session.user) { + req.session.appRequest = {}; -/** - * cancel app request and clear it - * @url /api/cancel - * @method GET - */ -route.get('/cancel', (req, res) => { - // if user is logged in - if(req.session && req.session.user) { - req.session.appRequest = {}; + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.request.operation.cancel.successful' + })); - return res.type('json').end(JSON.stringify({ - status: 200, - message: 'msg.request.operation.cancel.successful' - })); - - // user isnt logged in - } else { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - } -}); - -/** - * redirect user to app - * @url /api/redirect - * @method GET - * @GET ['id'] - */ -route.get('/redirect', (req, res) => { - // if user is logged in - if(req.session && req.session.user) { - // missing query data to retrieve app - if(!req.query || !req.query.id) { - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.request.data.missing' - ] + // user isnt logged in + } else { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' })); } + }); + + /** + * redirect user to app + * @url /api/redirect + * @method GET + * @GET ['id'] + */ + route.get('/redirect', asyncer(async (req, res) => { + // if user is logged in + if(req.session && req.session.user) { + // missing query data to retrieve app + if(!req.query || !req.query.id) { + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.request.data.missing' + ] + })); + } + + // set auth code + authCode = await db.setAuthCode({ + aId: req.query.id, + uId: req.session.user.id + }); - // set auth code - global['modules'].database.setAuthCode({ - aId: req.query.id, - uId: req.session.user.id - }, (err, rep) => { // database error - if(err) { - global['logs'].debug(err); + if(authCode.err) { + global['logs'].debug(authCode[1]); return res.type('json').status(500).end(JSON.stringify({ status: 500, message: [ @@ -165,25 +173,24 @@ route.get('/redirect', (req, res) => { } else if(rep) { // retrieve apps - global['modules'].database.getApps((err2, rep2) => { - // database error - if(err2) { - global['logs'].debug(err2); - return res.type('json').status(500).end(JSON.stringify({ - status: 500, - message: [ - 'msg.database.error' - ] - })); + apps = await db.getApps(); + // database error + if(apps.reply) { + global['logs'].debug(apps.err); + return res.type('json').status(500).end(JSON.stringify({ + status: 500, + message: [ + 'msg.database.error' + ] + })); + } + // for each app + apps.reply.forEach((app) => { + // if app.id is equal to queried app + if(app.id == req.query.id) { + // redirect to app + return res.redirect(app.access+"?uid="+req.session.user.id+"&token="+rep.token); } - // for each app - rep2.forEach((app) => { - // if app.id is equal to queried app - if(app.id == req.query.id) { - // redirect to app - return res.redirect(app.access+"?uid="+req.session.user.id+"&token="+rep.token); - } - }); }); } else { // database error @@ -194,47 +201,51 @@ route.get('/redirect', (req, res) => { ] })); } - }); - // user isnt logged in - } else { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - } -}); + // user isnt logged in + } else { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + } + })); -/** - * logout user - * @url /api/logout - * @method GET - */ -route.get('/logout', (req, res) => { - // user needs to be logged in - if(!req.session || !req.session.user) { - return res.type('json').end(JSON.stringify({ - status: 401, - message: 'msg.auth.login.required' - })); - // logout user - } else { - res.clearCookie('RememberMe'); - req.session.destroy(); - return res.type('json').end(JSON.stringify({ - status: 200, - message: 'msg.auth.logout.successful' - })); - } -}); - -if(global['gds'].debug) { - // DEBUG info - route.get('/info', (req, res) => { - let obj = {}; - if(req.session) obj.session = req.session; - if(req.cookies) obj.cookie = req.cookies; - res.type('json').end(JSON.stringify(obj)); + /** + * logout user + * @url /api/logout + * @method GET + */ + route.get('/logout', (req, res) => { + // user needs to be logged in + if(!req.session || !req.session.user) { + return res.type('json').end(JSON.stringify({ + status: 401, + message: 'msg.auth.login.required' + })); + // logout user + } else { + res.clearCookie('RememberMe'); + req.session.destroy(); + return res.type('json').end(JSON.stringify({ + status: 200, + message: 'msg.auth.logout.successful' + })); + } }); -} -module.exports = route; + if(global['gds'].debug) { + // DEBUG info + route.get('/info', (req, res) => { + let obj = {}; + if(req.session) obj.session = req.session; + if(req.cookies) obj.cookie = req.cookies; + res.type('json').end(JSON.stringify(obj)); + }); + } + + return route; +}; + +module.exports = { + getRoutes: getRoutes +}; diff --git a/bin/web/routes/static.js b/bin/web/routes/static.js index e1c500d..0e4de69 100644 --- a/bin/web/routes/static.js +++ b/bin/web/routes/static.js @@ -4,12 +4,16 @@ * (c) Ruben Meyer */ -var express = require('express'); -var route = express.Router(); +express = require('express'); +route = express.Router(); +asyncer = require('express-async-handler'); -var fs = require('fs'); -var path = require('path'); +fs = require('fs'); +path = require('path'); + + +// reduce IO file checks - save file state in cache var fileCheck = (file) => { if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {}; let dir = global['__dirname'] + '/bin/web/views'; @@ -29,37 +33,41 @@ var fileCheck = (file) => { } }; -/** - * main page - * @url / - * @method all - */ -route.all('/', function(req, res, next) { - // TODO: show login page or dashboard - // res.end('login or dashboard'); - global['modules'].database.getApps((err, rep) => { +let getRoutes = async () => { + let db = global['requireModule']('database'); + await db.connect(); + + /** + * main page + * @url / + * @method all + */ + route.all('/', asyncer(async (req, res, next) => { + // TODO: show login page or dashboard + // res.end('login or dashboard'); + apps = await db.getApps(); res.render('index', { session: req.session, - apps: rep + apps: apps.reply }); - }) -}); + })); -/** - * login page or apprequest page - * @url / - * @method GET - */ -route.get('/authenticate', (req, res) => { + /** + * login page or apprequest page + * @url / + * @method GET + */ + route.get('/authenticate', asyncer(async (req, res) => { - if(req.session) { - // if there isnt an apprequest - if(!req.session.appRequest) - req.session.appRequest = {}; // TODO: data - } + if(req.session) { + // if there isnt an apprequest + if(!req.session.appRequest) + req.session.appRequest = {}; // TODO: data + } + + // query apps + apps = await db.getApps(); - // query apps - global['modules'].database.getApps((err, rep) => { // set appId in appRequest if(req.query.appId) { if(req.query.appId && typeof req.query.appId == "string") { @@ -77,7 +85,7 @@ route.get('/authenticate', (req, res) => { res.render('request', { session: req.session, appRequest: req.session.appRequest, - apps: rep + apps: apps.reply }); // if user isnt logged in, show login page } else { @@ -85,7 +93,7 @@ route.get('/authenticate', (req, res) => { let view_obj = { session: req.session }; if(req.query.appId) { - rep.forEach((app) => { + apps.reply.forEach((app) => { if(app._id == req.query.appId) view_obj["login_title"] = "Login to use "+app.name+" via authRxbn"; // appRequest app name }) @@ -94,78 +102,84 @@ route.get('/authenticate', (req, res) => { res.render('login', view_obj); } - }); -}); + })); -/** - * all other routes - * @url /* - * @method all - * @TODO comments - */ -route.all('/*', (req, res, next) => { - // passthrough to next route - if(req.path.startsWith('/api')) - return next(); + /** + * all other routes + * @url /* + * @method all + * @TODO comments + */ + route.all('/*', (req, res, next) => { + // passthrough to next route + if(req.path.startsWith('/api')) + return next(); - if(req.path == "/request") return res.render('error/404'); + if(req.path == "/request") return res.render('error/404'); - let pathRules = require("./rules"); + let pathRules = require("./rules"); - let group = "anon"; - if(req.session && req.session.user) { - group = "user"; - if(req.session.user.group == 999) group = "admin"; - } + let group = "anon"; + if(req.session && req.session.user) { + group = "user"; + if(req.session.user.group == 999) group = "admin"; + } - pathRules.forEach((rule) => { - if(rule.rule == "block") { - if(group == rule.group) { - let regex = new RegExp(rule.expression, "g"); - if(regex.test(req.path)) { - if(rule.type == "404") { - return res.status(404).render('error/404', { - error_code: 404, - error_msg: 'msg.request.file.not_found', - session: req.session - }); - } else if(rule.type == "missing_permission") { - return res.status(401).render('error/permission', { - error_code: 401, - session: req.session - }); - } else if(rule.type == "login") { - return res.status(401).render('error/login', { - error_code: 401, - session: req.session - }); - } else { - return res.status(401).render('error/error', { - error_code: 401, - session: req.session - }); + pathRules.forEach((rule) => { + if(rule.rule == "block") { + if(group == rule.group) { + let regex = new RegExp(rule.expression, "g"); + if(regex.test(req.path)) { + if(rule.type == "404") { + global['logs'].info("[web] (404) path not found: "+req.path); + return res.status(404).render('error/404', { + error_code: 404, + error_msg: 'msg.request.file.not_found', + session: req.session + }); + } else if(rule.type == "missing_permission") { + return res.status(401).render('error/permission', { + error_code: 401, + session: req.session + }); + } else if(rule.type == "login") { + return res.status(401).render('error/login', { + error_code: 401, + session: req.session + }); + } else { + return res.status(401).render('error/error', { + error_code: 401, + session: req.session + }); + } } } } + }); + + if(fileCheck(req.path)) { + return res.render(req.path.replace(/^\//, ''), { + session: req.session, + cfg: global['gds'].cfg + }); + } else { + global['logs'].info("[web] (404) path not found: "+req.path); + return res.status(404).render('error/404', { + error_code: 404, + error_msg: 'msg.request.file.not_found', + session: req.session + }); } + + // TODO: try to login + // TODO: role-based authorization + // TODO: show login page or page }); - if(fileCheck(req.path)) { - return res.render(req.path.replace(/^\//, ''), { - session: req.session, - cfg: global['gds'].cfg - }); - } else { - return res.status(404).render('error/404', { - error_code: 404, - error_msg: 'msg.request.file.not_found', - session: req.session - }); - } + return route; +}; - // TODO: try to login - // TODO: role-based authorization - // TODO: show login page or page -}); - -module.exports = route; +module.exports = { + getRoutes: getRoutes +}; diff --git a/package.json b/package.json index ac1eff0..a6d72d2 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,12 @@ "license": "", "dependencies": { "body-parser": "^1.19.0", - "chalk": "^4.1.0", "cookie-parser": "^1.4.5", "express": "^4.17.1", + "express-async-handler": "^1.1.4", "express-session": "^1.17.1", "mongo-sanitize": "^1.1.0", "mongoose": "^5.9.28", - "pug": "^3.0.0", - "vorpal": "^1.12.0" + "pug": "^3.0.0" } }