/* * This file is part of the authRxbn eco-system. * * (c) Ruben Meyer * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ // init var mongoose = require('mongoose'); var sanitize = require('mongo-sanitize'); var crypto = require('crypto'); var methods = {}; var log = require(global['__dirname']+'/bin/logs/module'); var cfg = require(global['__dirname']+'/bin/config'); var db; var mdls = require('./models.js'); var models; /** * connects to db * @author Ruben Meyer * @async */ methods.connect = async () => { if(typeof db !== "undefined") return; // 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 }); } /** * returns db instance * @author Ruben Meyer * @return {Object} mongoose */ methods.getConnection = () => { return db; } // // // //////// //////// /////// // // // // // // // // // // ////// ////// ////// // // // // // // // // //////// ////// //////// // // // ///////////////////////////////////////////// /** * Adds User to Database * @author Ruben Meyer * @async * @param {String} nick nickname * @param {String} email email * @param {String} passhash hashed password * @param {Number} group Group ObjectId * @return {Object} async (reply, err) */ 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; let user = new userModel(); user.nickname = sanitize(nick); user.email = sanitize(email); user.passhash = sanitize(passhash); user.group = sanitize(group); try { reply = await user.save(); return {reply: 1}; } catch(err) { return {err: err}; } }; /** * deletes user identified by haystack from database * @author Ruben Meyer * @async * @TODO add functionality * @param {String} haystack email or nick * @return {Object} async(reply, err) */ 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); 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 * @async * @return {Object} async(reply, err) */ methods.getUsers = async () => { let userModel = models.user; try { users = await userModel.find({}).exec(); if(users.length > 0) return {reply: users}; else return {reply: false}; } catch(err) { return {err: err}; } }; /** * query users by UUID, email, nickname or rememberme token * @author Ruben Meyer * @async * @param {String|String[]} haystack email or nick * @return {Object} async(reply, err) */ methods.getUser = async (haystack) => { if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('haystack is not a string|object::database.getUser('+haystack+')', module.filename)}; let userModel = models.user; // sanitize input haystack = sanitize(haystack); let or = []; if(haystack instanceof mongoose.Types.ObjectId) { or.push({_id: haystack}); } else if(typeof haystack === 'string') { or = [{nickname: haystack}, {email: haystack}, {token: haystack}]; if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack)}); } else { or = []; for(let i = 0; i < haystack.length; i++) { if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack[i])}); or.push({nickname: haystack[i]}); or.push({email: haystack[i]}); or.push({token: haystack[i]}); } } try { users = await userModel.find().or(or).exec(); if(users.length == 1) return {reply: users[0]}; else 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 * @return {Object} async(reply, err) */ methods.updateUser = async (id, obj) => { if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) 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; try { data = await userModel.findByIdAndUpdate(id, obj).exec(); return {reply: data}; } catch(err) { return {err: err}; } }; /** * 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 * @return {Object} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err) */ methods.addActivity = async (id, data) => { if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) 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(); 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; 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 { return {reply: { date: date, timestamp: timestamp, token: options.old_token }}; } } catch(err) { return {err: err}; } }; // //////// /////// //////// // // /////// ////// // // // // // // // // // // // // //////// /////// // // // // /////// ////// // // // // // // // // // // // // //////// // // //////// //////// // ////// // ///////////////////////////////////////////////////////////////// /** * get all groups * @author Ruben Meyer * @async * @return {Object} async(reply, err) */ methods.getGroups = async () => { let groupModel = models.group; try { groups = await groupModel.find({}).exec(); if(groups.length > 0) return {reply: groups}; else return {reply: false}; } catch(err) { return {err: err}; } }; /** * query groups by UUID or name * @author Ruben Meyer * @async * @param {String|String[]} haystack UUID or name * @return {Object} async(reply, err) */ methods.getGroup = async (haystack) => { if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('haystack is not a string|object::database.getGroup('+haystack+')', module.filename)}; let groupModel = models.group; // sanitize input haystack = sanitize(haystack); let or = []; if(haystack instanceof mongoose.Types.ObjectId) { or.push({_id: haystack}); } else if(typeof haystack === 'string') { or = [{name: haystack}]; if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack)}); } else { or = []; for(let i = 0; i < haystack.length; i++) { if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack[i])}); or.push({name: haystack[i]}); } } try { groups = await groupModel.find().or(or).exec(); if(groups.length == 1) return {reply: groups[0]}; else return {reply: false}; } catch(err) { return {err: err}; } }; /** * Adds Group to Database * @author Ruben Meyer * @async * @param {String} name name * @param {String} roles roles * @return {Object} async (reply, err) */ methods.addGroup = async (name, roles) => { if(typeof name !== 'string') return {err: new TypeError('name is not a string::database.addGroup('+name+','+roles+')', module.filename)}; if(typeof roles !== 'string') return {err: new TypeError('roles is not a string::database.addGroup('+name+','+roles+')', module.filename)}; let groupModel = models.group; let group = new groupModel(); // sanitize input group.name = sanitize(nick); group.roles = sanitize(email); try { reply = await group.save(); return {reply: 1}; } catch(err) { return {err: err}; } }; /** * updates obj keys in group entry * @author Ruben Meyer * @async * @param {Number} id Group ID * @param {Object} obj data * @return {Object} async(reply, err) */ methods.updateGroup = async (id, obj) => { if(!(typeof id === 'string' || id instanceof mongoose.Types.ObjectId)) return {err: new TypeError('id is not a string::database.updateGroup('+id+','+JSON.stringify(obj)+')', module.filename)}; if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.updateGroup('+id+','+JSON.stringify(obj)+')', module.filename)}; let groupModel = models.group; try { data = await groupModel.findByIdAndUpdate(id, obj).exec(); return {reply: data}; } catch(err) { return {err: err}; } }; /** * delete group and set fallback group for users * @author Ruben Meyer * @async * @param {String} id group id * @param {String} fallbackId fallback group id * @return {Boolean} */ methods.delGroup = async (id, fallbackId) => { if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.delGroup('+id+','+ fallbackId +')', module.filename)}; if(typeof fallbackId !== 'string') return {err: new TypeError('fallbackId is not a string::database.delGroup('+id+','+ fallbackId +')', module.filename)}; let groupModel = models.group; let userModel = models.user; let pathModel = models.pathRules; // sanitize input id = sanitize(id); fallbackId = sanitize(fallbackId); try { try { // find users users = await userModel.find({group: id}).exec(); // set fallback group for each user users.forEach(async (user) => { await userModel.findByIdAndUpdate(user._id, {group: fallbackId}).exec(); }); // find rules rules = await pathModel.find({group: id}).exec(); // set fallback group for each rule rules.forEach(async (rule) => { await pathModel.findByIdAndUpdate(rule._id, {group: fallbackId}).exec(); }); // remove group reply = await groupModel.findByIdAndRemove(id).exec(); return {reply: 1}; } catch (e) { return {err: e}; } } catch(err) { return {err: err}; } }; // //////// //////// ////////// // // //////// // // // // // // // // // // //////// // // // // // // // // //////// // //////// //////// // // // // // // // // // // // // // // // //////// // //////////////////////////////////////////////////////////// /** * get pathrules * @author Ruben Meyer * @async * @return {Object} async(rules, err) */ methods.getPathRules = async () => { var PathRules = models.pathRules; try { rules = await PathRules.find({}).exec(); return {reply: rules}; } catch(err) { return {err: err}; } }; // //////// //////// //////// ////// // // // // // // // // // //////// /////// //////// ////// // // // // // // // // // // // ////// // ////////////////////////////////////////////// /** * get Applications * @author Ruben Meyer * @async * @return {Object} async(apps, err) */ methods.getApps = async () => { var Application = models.application; try { apps = await Application.find({}).exec(); return {reply: apps}; } catch(err) { return {err: err}; } }; /** * return auth obj * @author Ruben Meyer * @async * @TODO * @param {Object} obj data obj (aId, uId) * @return {Object} async({timestamp, token}, err) */ 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; let query = { applicationId: obj.aId, userId: obj.uId }; let change = { token: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => (c ^ crypto.randomBytes(new Uint8Array(1).length)[0] & 15 >> c / 4).toString(16)), timestamp: Date.now() }; 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 * @TODO * @param {Object} obj data obj (aId, aSecret, uId, token) * @return {Object} async(bool, err) */ 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; 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 { if(!(obj.aId instanceof mongoose.Types.ObjectId)) obj.aId = mongoose.Types.ObjectId(obj.aId); data1 = await Application.findOne({ $and: [ {_id: obj.aId}, {secret: obj.aSecret} ] }).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 app permission * @author Ruben Meyer * @async * @TODO * @param {Object} obj data obj (aId, redirectUrl) * @return {Object} async(bool, err) */ methods.verifyAppCall = async (obj) => { return {}; }; // //////// //////// //////// //////// //////// // // // // // // // // //////// // // // // //////// // // // //////// // // // //////// // // // // //////// // //////////////////////////////////////////////////////// /** * returns user count * @author Ruben Meyer * @async * @return {Object} async(int, err) */ methods.userCount = async () => { let userModel = models.user; try { count = await userModel.countDocuments({}).exec(); return {reply: count}; } catch(err) { return {err: err}; } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// module.exports = methods;