408 lines
10 KiB
JavaScript
408 lines
10 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
|
|
});
|
|
}
|
|
|
|
// // // //////// //////// ///////
|
|
// // // // // // //
|
|
// // // ////// ////// //////
|
|
// // // // // // //
|
|
// //////// ////// //////// // //
|
|
//
|
|
/////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* 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 id (normally 0 -> user)
|
|
* @return {Array} 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 {Array} 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 {Array} 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 async(reply, err)
|
|
*/
|
|
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;
|
|
|
|
// sanitize input
|
|
haystack = sanitize(haystack);
|
|
|
|
let or = [];
|
|
if(typeof haystack === 'string') {
|
|
or = [{nickname: haystack}, {email: haystack}, {token: haystack}];
|
|
if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: haystack});
|
|
}
|
|
else {
|
|
or = [];
|
|
for(let i = 0; i < haystack.length; i++) {
|
|
if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: 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 > 0)
|
|
return {reply: users};
|
|
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 {Array} async(reply, err)
|
|
*/
|
|
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;
|
|
|
|
try {
|
|
data = await userModel.findByIdAndUpdate(id, obj).exec();
|
|
return {data: 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 {Array} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err)
|
|
*/
|
|
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();
|
|
|
|
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 Applications
|
|
* @author Ruben Meyer
|
|
* @async
|
|
* @return {Array} 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
|
|
* @param {Object} obj data obj (aId, uId)
|
|
* @return {Array} 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
|
|
* @param {Object} obj data obj (aId, aSecret, uId, token)
|
|
* @return {Array} 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 {
|
|
data1 = await Application.findOne({
|
|
$and: [
|
|
{_id: mongoose.Types.ObjectId(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 if app is permitted to do access call
|
|
* @author Ruben Meyer
|
|
* @async
|
|
* @param {Object} obj data obj (aId, redirectUrl)
|
|
* @return {Array} async(bool, err)
|
|
*/
|
|
methods.verifyAppCall = async (obj) => {
|
|
return {};
|
|
};
|
|
|
|
// //////// //////// //////// //////// ////////
|
|
// // // // // // //
|
|
// //////// // // // // ////////
|
|
// // // //////// // //
|
|
// //////// // // // // ////////
|
|
//
|
|
////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* returns user count
|
|
* @author Ruben Meyer
|
|
* @async
|
|
* @return {Array} 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;
|