1
0
Fork 0
auth.rxbn.de/bin/database/module.js

624 lines
16 KiB
JavaScript

/*
* This file is part of the authRxbn eco-system.
*
* (c) Ruben Meyer <contact@rxbn.de>
*
* 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;