1
0
Fork 0

Compare commits

...

6 Commits

15 changed files with 527 additions and 882 deletions

20
app.js
View File

@ -8,17 +8,15 @@
// GDS: Global Data System // GDS: Global Data System
global['gds'] = { global['gds'] = {
debug: (process.env.NODE_ENV === 'debug') ? true : false, debug: (process.env.NODE_ENV === 'debug') ? true : false,
db: null,
cache: {}, cache: {},
cfg: require(__dirname+'/bin/config') cfg: require(__dirname+'/bin/config')
}; };
global['modules'] = {};
global['__dirname'] = __dirname; global['__dirname'] = __dirname;
/** /**
* load modules * load modules
*/ */
let load = (name) => { let load = global['requireModule'] = (name) => {
return require(__dirname+'/bin/'+name+'/module'); return require(__dirname+'/bin/'+name+'/module');
}; };
@ -33,17 +31,9 @@ env_vars.forEach((el) => {
}); });
if(env_missing) process.exit(); if(env_missing) process.exit();
global['modules'].events = load('events'); // event handler load('events'); // event handler
global['modules'].cli = load('cli'); // command line interface
global['modules'].logs = load('logs'); // log handler global['logs'] = load('logs'); // log handler
global['logs'] = global['modules'].logs; // alias
global['modules'].database = load('database'); // database service let web = load('web'); // web server
global['modules'].web = load('web'); // web server web.start(); // start web server
global['modules'].auth = load('auth'); // authentication handler
global['logs'] = global['modules'].logs; // alias
// start web server
global['modules'].web.start();

View File

@ -8,8 +8,11 @@
var methods = {}; var methods = {};
var crypto = require('crypto'); var crypto = require('crypto');
var cfg = require(global['__dirname']+'/bin/config');
delimiter = cfg.app.passhashDelimiter;
/** /**
* Generating Hash * returns a hash|salt combination
* @author Ruben Meyer * @author Ruben Meyer
* @param {String} key "user password" * @param {String} key "user password"
* @param {String} salt (OPTIONAL) * @param {String} salt (OPTIONAL)
@ -27,11 +30,11 @@ methods.generateHash = (key, salt) => {
hash.update(key); hash.update(key);
hash = hash.digest('hex'); hash = hash.digest('hex');
return hash+'|'+salt; return hash+delimiter+salt;
}; };
/** /**
* validate hashed password * validates a hashed input
* @author Ruben Meyer * @author Ruben Meyer
* @param {String} hash "hashed password" * @param {String} hash "hashed password"
* @param {String} key "plaintext password" * @param {String} key "plaintext password"
@ -40,15 +43,15 @@ methods.generateHash = (key, salt) => {
methods.validateHash = (hash, key) => { methods.validateHash = (hash, key) => {
if(typeof hash !== 'string' || typeof key !== 'string') return false; if(typeof hash !== 'string' || typeof key !== 'string') return false;
let salt = hash.split('|')[1]; let salt = hash.split(delimiter)[1];
let generated = methods.generateHash(key, salt); let generated = methods.generateHash(key, salt);
if( if(
hash.split('|')[0].length === generated.split('|')[0].length hash.split(delimiter)[0].length === generated.split(delimiter)[0].length
&& &&
crypto.timingSafeEqual( crypto.timingSafeEqual(
Buffer.from(generated.split('|')[0], 'hex'), Buffer.from(generated.split(delimiter)[0], 'hex'),
Buffer.from(hash.split('|')[0], 'hex') Buffer.from(hash.split(delimiter)[0], 'hex')
) )
) { ) {
return true; return true;

View File

@ -1,52 +0,0 @@
/*
* This file is part of the authRXBN single sign-on package.
*
* (c) Ruben Meyer <contact@rxbn.de>
*/
module.exports = {
'command': 'cache [action] [type]',
'description': 'do something with the cache',
'actionDependencies': ['vorpal'],
'action': (actionDependencies) => {
let vorpal = actionDependencies.vorpal;
return (args, cb) => {
if(typeof args.action !== 'undefined') {
let action = args.action.toLowerCase();
if(typeof args.type !== 'undefined') { var type = args.type.toLowerCase(); }
else { var type = ''; }
//Object.keys(args.options).length > 0
if(action == 'help' || action == '?') {
vorpal.exec('cache --help');
}
else if(action == 'flush') {
if(typeof global['gds'].cache[type] !== "undefined") {
global['gds'].cache[type] = {};
console.log(type+' cache flush');
} else if(type == 'all') {
global['gds'].cache = {};
console.log('full cache flush');
}
else vorpal.exec('cache --help');
}
else if(action == 'get') {
console.log(Object.keys(global['gds'].cache));
console.log(type);
console.log(type in Object.keys(global['gds'].cache));
console.log(Object.keys(global['gds'].cache).hasOwnProperty(type));
if(typeof global['gds'].cache[type] !== "undefined") {
console.log(JSON.stringify(global['gds'].cache[type]));
} else if(type == 'all'){
console.log(JSON.stringify(global['gds'].cache));
} else {
console.log("The cache \""+type+"\" doesnt exists.");
vorpal.exec('cache --help');
}
} else vorpal.exec('cache --help');
} else vorpal.exec('cache --help');
cb();
}
}
};

View File

@ -1,14 +0,0 @@
/*
* This file is part of the authRXBN single sign-on package.
*
* (c) Ruben Meyer <contact@rxbn.de>
*/
module.exports = {
'command': 'clear',
'description': 'clear the output',
'action': (args, cb) => {
process.stdout.write ("\u001B[2J\u001B[0;0f");
cb();
}
};

View File

@ -1,14 +0,0 @@
/*
* This file is part of the authRXBN single sign-on package.
*
* (c) Ruben Meyer <contact@rxbn.de>
*/
module.exports = {
'command': 'logs',
'description': 'output logs',
'action': (args, cb) => {
console.log(JSON.stringify(global['gds'].cache.logs));
cb();
}
};

View File

@ -1,238 +0,0 @@
/*
* This file is part of the authRXBN single sign-on package.
*
* (c) Ruben Meyer <contact@rxbn.de>
*/
module.exports = {
'command': 'user <action> <nick> [data...]',
'description': 'add, get, update or remove an user',
'actionDependencies': ['vorpal'],
'action': (actionDependencies) => {
let vorpal = actionDependencies.vorpal;
return (args, cb) => {
if(typeof args.action !== 'undefined') {
let action = args.action.toLowerCase();
let profile = {
user: args.nick,
pass: null,
mail: null,
group: 0
};
// add a new user
if(action === 'add') {
if(Array.isArray(args.data) && args.data.length >= 1) {
// set data
profile.pass = global['modules'].auth.generateHash(args.data[0]);
if(args.data.length >= 2) profile.mail = args.data[1];
if(args.data.length >= 3) profile.group = args.data[2];
// haystack verifying
let haystack = profile.user;
if(typeof profile.mail !== 'undefined' && profile.mail !== null) haystack = [profile.user, profile.mail];
// query user by haystack
global['modules'].database.getUser(haystack, (err, rep) => {
if(err) {
global['logs'].error("ERR: While finding user");
global['logs'].error(err);
}
else {
// no users exist, add user
if (!rep) {
global['modules'].database.addUser(profile.user, (profile.mail || ''), profile.pass, profile.group, (errAdd, repAdd) => {
if(errAdd) {
global['logs'].error("ERR: While adding user");
global['logs'].error(errAdd);
}
else vorpal.log("Reply: "+repAdd);
});
// user already exists
} else {
vorpal.log("User exists: ");
rep.forEach((el, i) => {
el["passhash"] = undefined; // not needed
rep[i] = el;
});
vorpal.log(rep);
}
}
});
// missing data
} else {
global['logs'].log("No data is present or is missing. Please see:");
global['logs'].log("$ user help add");
cb();
}
// query users
} else if(action === 'get' || action === 'ls') {
// wildcard catch-all
if(profile.user === '*') {
global['modules'].database.getUsers((err, rep) => {
if(rep) {
rep.forEach((el, i) => {
el["passhash"] = undefined; // not needed
rep[i] = el;
});
global['logs'].log(rep);
}
if(err) {
global['logs'].error('$ user get *');
global['logs'].error(err);
}
});
} else {
// query users by first input <nickname>
global['modules'].database.getUser(profile.user, (err, rep) => {
// user exists
if(rep && rep.length == 1) {
global['logs'].log("User exists: ");
rep.forEach((el, i) => {
el["passhash"] = undefined; // not needed
rep[i] = el;
});
global['logs'].log(rep);
} else {
// found more than one user
if(rep && rep.length >= 2) {
global['logs'].warn("multiple users found for "+profile.user+".");
rep.forEach((el) => {
global['logs'].warn("found user with id: "+el._id);
});
// user does not exist
} else {
global['logs'].warn("User "+profile.user+" doesn't exist.");
}
}
// query error
if(err) {
global['logs'].error('$ user get '+profile.user);
global['logs'].error(err);
}
});
}
// update users, just one property
} else if(action === 'update') {
if(args.data.length < 2) global['logs'].error("No data supplied.");
else {
let property = args.data[0];
let param = args.data[1];
global['logs'].debug("Prop: "+property+"; Param: "+param);
// query user
global['modules'].database.getUser(profile.user, (err, rep) => {
// user exists
if(rep && rep.length == 1) {
let obj = {};
obj[property] = param;
global['modules'].database.updateUser(String(rep[0]._id), obj, (errUpd, repUpd) => {
// updated user
if(repUpd) {
global['logs'].log("User with id "+String(rep[0]._id)+" was updated.");
// user not updated
} else {
global['logs'].warn("User with id "+String(rep[0]._id)+" doesn't exist.");
}
// query error
if(errUpd) {
global['logs'].error('$ user update '+profile.user+' '+property+' '+param+' [on update]');
global['logs'].error(errUpd);
}
});
} else {
// found more than one user
if(rep && rep.length >= 2) {
global['logs'].warn("multiple users found for "+profile.user+". bad state. can't update.");
rep.forEach((el) => {
global['logs'].warn("found user with id: "+el._id);
});
// user does not exist
} else {
global['logs'].warn("User "+profile.user+" doesn't exist.");
}
}
// query error
if(err) {
global['logs'].error('$ user update '+profile.user+' '+field+' '+param+' [on query]');
global['logs'].error(err);
}
});
}
// remove users
} else if(action === 'remove' || action === 'delete') {
// haystack
let haystack = profile.user;
if(typeof profile.mail !== 'undefined' && profile.mail !== null) haystack = [profile.user, profile.mail];
// query user
global['modules'].database.getUser(haystack, (err, rep) => {
if(rep) {
vorpal.log("user exists. deleting him.");
global['logs'].debug(rep);
// remove user
global['modules'].database.delUser(rep[0].email, (errDel, repDel) => {
if(repDel) {
vorpal.log("deleted user.");
global['logs'].debug(repDel);
}
else {
vorpal.log("ERR: while deleting user.");
global['logs'].debug(errDel);
}
});
}
});
} else if(action === 'help') {
if(args.nick === 'add') {
vorpal.log("user add <nickname> <raw password> <mail> [group]");
vorpal.log("<nickname>: user nickname");
vorpal.log("<raw password>: will be hashed ASAP");
vorpal.log("<mail>: format: user@example.tld");
vorpal.log("[group]: not needed; only Numbers; group id");
vorpal.log("---");
vorpal.log("returns 0 or 1 and printing errors");
} else if(args.nick === 'get') {
vorpal.log("user get <nickname or email>");
vorpal.log("<nickname or email>: searching both in both; format: foobar OR user@example.tld");
vorpal.log("---");
vorpal.log("user get * - to get all users");
vorpal.log("prints JSON-object of user data");
} else if(args.nick === 'update') {
vorpal.log("user update <nickname> <field> <parameter>");
vorpal.log("<nickname>: user nickname");
vorpal.log("<field>: string");
vorpal.log("<parameter>: mixed data; will be converted to Boolean, Number or String");
vorpal.log("---");
vorpal.log("returns 0 or 1 and printing errors");
vorpal.log("prints JSON-object of updated user data");
} else if(args.nick === 'remove' || args.nick === 'delete') {
vorpal.log("user remove|delete <nickname>");
vorpal.log("<nickname>: user nickname or mail");
vorpal.log("---");
vorpal.log("returns 0 or 1 and printing errors");
}
}
cb();
} else {
vorpal.exec('user --help');
cb();
}
}
}
};

View File

@ -1,94 +0,0 @@
/*
* This file is part of the authRXBN single sign-on package.
*
* (c) Ruben Meyer <contact@rxbn.de>
*/
let vorpal = require('vorpal')();
let chalk = require('chalk');
let fs = require('fs');
/**
* read command files and interpret them
* @author Ruben Meyer
* @todo options, types, hide command, parsing, help, autocompletion, allow unknown options
*/
let cmdPath = global['__dirname']+'/bin/cli/cmds';
fs.readdir(cmdPath, (err, files) => {
if(files.length > 0)
files.forEach((file) => {
let cmd = require(cmdPath+'/'+file); // read file
// exported data is an object
if(typeof cmd == 'object' && typeof cmd.command !== 'undefined') {
// set initial building steps
let builder = vorpal.command(cmd.command);
// description
if(typeof cmd.description !== 'undefined') builder = builder.description(cmd.description);
// aliases
if(typeof cmd.alias !== 'undefined') {
if(typeof cmd.alias === 'object' && Array.isArray(cmd.alias)) builder = builder['alias'](...cmd.alias);
if(typeof cmd.alias === 'string' && cmd.alias.split(',').length >= 2) {
let args = cmd.alias.split(',');
for(let i = 0; i < args.length; i++)
args[i] = args[i].trim();
builder = builder['alias'](...cmd.alias);
}
}
// action
if(typeof cmd.action !== 'undefined' && typeof cmd.action === 'function') {
if(typeof cmd.actionDependencies !== 'undefined') {
let dependencies = [];
let actionDependencies = {};
// format input
if(typeof cmd.actionDependencies === 'object') {
if(Array.isArray(cmd.actionDependencies))
cmd.actionDependencies.forEach((dependency) => dependencies.push(dependency));
} else if(typeof cmd.actionDependencies === 'string') {
let strArr = cmd.actionDependencies.split(',');
for(let i = 0; i < strArr.length; i++)
dependencies.push(strArr[i].trim());
}
// retrieve dependencies; unknown dependencies wont be handled
dependencies.forEach((dependency) => {
switch(dependency) {
case 'vorpal':
actionDependencies['vorpal'] = vorpal;
break;
case 'chalk':
actionDependencies['chalk'] = chalk;
break;
}
});
builder = builder['action'](cmd.action(actionDependencies));
} else {
builder = builder['action'](cmd.action);
}
}
}
});
});
/**
@TODO remove code
isJson = (str) => {
try {
let o = JSON.parse(str);
return (o && typeof o === "object") ? true : false;
} catch (e) {
return false;
}
};
*/
vorpal.delimiter('auth@rxbn$').show();
module.exports = vorpal;

View File

@ -27,6 +27,7 @@ module.exports = {
}, },
app: { app: {
locale: 'de-DE', // default locale (de-DE & en-EN should be available) locale: 'de-DE', // default locale (de-DE & en-EN should be available)
passhashDelimiter: '|'
}, },
mongoose: { mongoose: {
uri: process.env.DB_URL, uri: process.env.DB_URL,

View File

@ -13,28 +13,40 @@ var sanitize = require('mongo-sanitize');
var crypto = require('crypto'); var crypto = require('crypto');
var methods = {}; var methods = {};
// connect var log = require(global['__dirname']+'/bin/logs/module');
mongoose.connect(global['gds'].cfg.mongoose.uri, { var cfg = require(global['__dirname']+'/bin/config');
useNewUrlParser: true
});
mongoose.set('useFindAndModify', false);
global['gds'].db = mongoose.connection;
var db;
var mdls = require('./models.js'); 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 // connect
global['gds'].db.on('error', (data) => { mongoose.connect(cfg.mongoose.uri, {
global['modules'].logs.error('MongoDB connection error:\n', data); useNewUrlParser: true,
process.exit(); // exit on connection error 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 * Adds User to Database
* @author Ruben Meyer * @author Ruben Meyer
* @param {String} nick nickname * @async
* @param {String} email email * @param {String} nick nickname
* @param {String} passhash hashed password * @param {String} email email
* @param {Number} group Group id (normally 0 -> user) * @param {String} passhash hashed password
* @param {Function} callback Callback function (error, reply) * @param {Number} group Group id (normally 0 -> user)
* @return {Array} async (reply, err)
*/ */
methods.addUser = (nick, email, passhash, group, callback) => { methods.addUser = async (nick, email, passhash, group) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof nick !== 'string') return {err: new TypeError('nick is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)};
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 {err: new TypeError('email is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', 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 {err: new TypeError('passhash is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', 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 {err: new TypeError('group is not a number::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)};
if(isNaN(group)) return callback(new TypeError('group is not a number::database.addUser('+nick+','+email+','+passhash+','+group+',callback)', module.filename));
let userModel = models.user; let userModel = models.user;
@ -69,70 +81,70 @@ methods.addUser = (nick, email, passhash, group, callback) => {
user.passhash = sanitize(passhash); user.passhash = sanitize(passhash);
user.group = sanitize(group); user.group = sanitize(group);
user.save((err) => { try {
if(!err) callback(null, 1); reply = await user.save();
else callback(err); return {reply: 1};
}); } catch(err) {
return {err: err};
}
}; };
/** /**
* Deletes User from Database * deletes user identified by haystack from database
* @author Ruben Meyer * @author Ruben Meyer
* @TODO * @async
* @TODO add functionality
* @param {String} haystack email or nick * @param {String} haystack email or nick
* @param {Function} callback Callback function (error, reply) * @return {Array} async(reply, err)
*/ */
methods.delUser = (haystack, callback) => { methods.delUser = async (haystack) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof haystack !== 'string') return {err: new TypeError('haystack is not a string::database.delUser('+haystack+')', module.filename)};
if(typeof haystack !== 'string') return callback(new TypeError('haystack is not a string::database.delUser('+haystack+',callback)', module.filename));
let userModel = models.user; let userModel = models.user;
// sanitize input // sanitize input
haystack = sanitize(haystack); haystack = sanitize(haystack);
userModel.findOneAndDelete().or([{nickname: haystack}, {email: haystack}]) try {
.then((rep) => { reply = await userModel.findOneAndDelete().or([{nickname: haystack}, {email: haystack}]).exec();
// TODO delete user log.debug('deleted user: '+haystack);
global['logs'].debug('deleted user: '+haystack); return {reply: 1};
callback(null, 1); } catch(err) {
}).catch((err) => { return {err: err};
callback(err); }
});
}; };
/** /**
* get all users * get all users
* @author Ruben Meyer * @author Ruben Meyer
* @param {Function} callback Callback function (reply -> Array users) * @async
* @return {Array} async(reply, err)
*/ */
methods.getUsers = (callback) => { methods.getUsers = async () => {
if(typeof callback !== 'function') callback = function() {};
let userModel = models.user; let userModel = models.user;
userModel.find({}) try {
.then((users) => { users = await userModel.find({}).exec();
if(users.length > 0) if(users.length > 0)
return callback(null, users); return {reply: users};
else else
return callback(null, false); return {reply: false};
}).catch((err) => { } catch(err) {
return callback(err); return {err: err};
}); }
}; };
/** /**
* query users by email, nickname or rememberme token * query users by UUID, email, nickname or rememberme token
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @param {String|String[]} haystack email or nick * @param {String|String[]} haystack email or nick
* @param {Function} callback Callback function (reply -> Array users) * @return async(reply, err)
*/ */
methods.getUser = (haystack, callback) => { methods.getUser = async (haystack) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof haystack !== 'string' && typeof haystack !== 'object') return {err: new TypeError('email or nickname is not a string|object::database.getUser('+haystack+')', module.filename)};
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));
let userModel = models.user; let userModel = models.user;
@ -154,35 +166,39 @@ methods.getUser = (haystack, callback) => {
} }
} }
userModel.find().or(or) try {
.then((users) => { users = await userModel.find().or(or).exec();
if(users.length > 0) if(users.length > 0)
return callback(null, users); return {reply: users};
else else
return callback(null, false); return {reply: false};
}).catch((err) => { } catch(err) {
return callback(err); return {err: err};
}); }
}; };
/** /**
* updates obj keys in user entry * updates obj keys in user entry
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @param {Number} id User ID * @param {Number} id User ID
* @param {Object} obj data * @param {Object} obj data
* @param {Function} callback Callback function * @return {Array} async(reply, err)
*/ */
methods.updateUser = (id, obj, callback) => { methods.updateUser = async (id, obj) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)};
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 {err: new TypeError('obj is not an object::database.updateUser('+id+','+JSON.stringify(obj)+')', module.filename)};
if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.updateUser('+id+','+JSON.stringify(obj)+',callback)', module.filename));
let userModel = models.user; let userModel = models.user;
userModel.findByIdAndUpdate(id, obj, (err, data) => {
if(err) callback(err); try {
else callback(null, data); 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 * updates data based on login
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @TODO UPDATE METHOD; PROBABLY OUTDATED * @TODO UPDATE METHOD; PROBABLY OUTDATED
* @param {Number} id User ID * @param {Number} id User ID
* @param {Object} data data JSON -> remember * @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) => { methods.addActivity = async (id, data) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof id !== 'string') return {err: new TypeError('id is not a string::database.updateNewAction('+id+','+JSON.stringify(options)+')', module.filename)};
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 {err: new TypeError('obj is not an object::database.updateUserProfile('+id+','+JSON.stringify(obj)+')', 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));
let date = new Date().toISOString(); let date = new Date().toISOString();
let timestamp = new Date(date).getTime(); let timestamp = new Date(date).getTime();
functions.updateUserProfile(id, { try {
last_action: date reply = await methods.updateUser(id, {
}, (err, rep) => { last_action: date
if(err) return callback(err); });
if(options.rememberme && options.new_token !== false) { 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 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; var Remember = models.remember;
Remember.findOneAndUpdate({userId: id}, {token: token, timestamp: Date.now()}, {upsert: true}, (err1, data) => {
if(err1) callback(err1); try {
return callback(null, { data = await Remember.findOneAndUpdate({userId: id}, {token: token, timestamp: Date.now()}, {upsert: true}).exec();
return {reply: {
date: date, date: date,
timestamp: timestamp, timestamp: timestamp,
token: token token: token
}); }};
}); }
catch(err) {
return {err: err};
}
} else { } else {
callback(null, { return {reply: {
date: date, date: date,
timestamp: timestamp, timestamp: timestamp,
token: options.old_token token: options.old_token
}); }};
} }
}); } catch(err) {
return {err: err};
}
}; };
@ -241,27 +263,28 @@ methods.addActivity = (id, data, callback) => {
/** /**
* get Applications * get Applications
* @author Ruben Meyer * @author Ruben Meyer
* @param {Function} callback Callback function (err, apps) * @async
* @return {Array} async(apps, err)
*/ */
methods.getApps = (callback) => { methods.getApps = async () => {
if(typeof callback !== 'function') callback = function() {};
var Application = models.application; var Application = models.application;
try {
Application.find({}, (err, apps) => { apps = await Application.find({}).exec();
if(err) callback(err); return {reply: apps};
else callback(null, apps); } catch(err) {
}); return {err: err};
}
}; };
/** /**
* return auth obj * return auth obj
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @param {Object} obj data obj (aId, uId) * @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) => { methods.setAuthCode = async (obj) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+')', module.filename)};
if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+',callback)', module.filename));
var AuthCode = models.authCode; var AuthCode = models.authCode;
@ -275,74 +298,80 @@ methods.setAuthCode = (obj, callback) => {
timestamp: Date.now() timestamp: Date.now()
}; };
AuthCode.findOneAndUpdate(query, change, {upsert: true}, (err1, data) => { try {
if(err1) callback(err1); data = await AuthCode.findOneAndUpdate(query, change, {upsert: true}).exec();
return callback(null, { return {reply: {
timestamp: change.timestamp, timestamp: change.timestamp,
token: change.token token: change.token
}); }};
}); } catch(err) {
return {err: err};
}
}; };
/** /**
* return auth obj * return auth obj
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @param {Object} obj data obj (aId, aSecret, uId, token) * @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) => { methods.getAuth = async (obj) => {
if(typeof callback !== 'function') callback = function() {}; if(typeof obj !== 'object') return {err: new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+')', module.filename)};
if(typeof obj !== 'object') return callback(new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+',callback)', module.filename));
var AuthCode = models.authCode; 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: [ $and: [
{_id: mongoose.Types.ObjectId(obj.aId)}, {_id: mongoose.Types.ObjectId(obj.aId)},
{secret: obj.aSecret} {secret: obj.aSecret}
] ]
}, (err1, data1) => { }).exec();
if(err1) callback(err1);
else { if(obj.token == data.token
if(obj.token == data.token && obj.aId == String(data.applicationId)
&& obj.aId == String(data.applicationId) && obj.uId == String(data.userId)
&& obj.uId == String(data.userId) && obj.aSecret == data1.secret) {
&& obj.aSecret == data1.secret) { return {reply: true};
callback(null, true); //methods.setAuthCode({
//functions.setAuthCode({ // aId: obj.aId,
// aId: obj.aId, // uId: obj.uId
// uId: obj.uId //});
//}, () => {}); }
} else return{reply: false};
else callback(null, false); } catch(err) {
} return {err: err};
}); }
} else callback(null, false); } else return {reply: false};
} } catch(err) {
}); return {err: err};
}
}; };
/** /**
* return if app is permitted to do access call * return if app is permitted to do access call
* @author Ruben Meyer * @author Ruben Meyer
* @async
* @param {Object} obj data obj (aId, redirectUrl) * @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 * @author Ruben Meyer
* @param {Function} callback Callback function (reply -> int) * @async
* @return {Array} async(int, err)
*/ */
methods.userCount = (callback) => { methods.userCount = async () => {
if(typeof callback !== 'function') callback = function() {};
let userModel = models.user; 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};
}
}; };

View File

@ -8,7 +8,7 @@
// GCS: Global Communication System // GCS: Global Communication System
let EventEmitter = require('events'); let EventEmitter = require('events');
global['gcs'] = new EventEmitter.EventEmitter(); global['event'] = new EventEmitter.EventEmitter();
/** /**
* Error Message Handler * Error Message Handler
@ -16,7 +16,7 @@ global['gcs'] = new EventEmitter.EventEmitter();
* @param {String} message data: "MODULE SEPARATOR MESSAGE" * @param {String} message data: "MODULE SEPARATOR MESSAGE"
* @return {void} * @return {void}
*/ */
global['gcs'].on('err', (data) => { global['event'].on('err', (data) => {
separator = "->"; separator = "->";
i = data.indexOf(separator); i = data.indexOf(separator);
parts = [data.slice(0, i), data.slice(i+separator.length)]; parts = [data.slice(0, i), data.slice(i+separator.length)];

View File

@ -2,12 +2,13 @@ var methods = {};
let fs = require('fs'); let fs = require('fs');
let util = require('util'); let util = require('util');
var cfg = require(global['__dirname']+'/bin/config');
// save new line to file // save new line to file
newLine = (prefix, obj) => { newLine = (prefix, obj) => {
let date = new Date(); // current date let date = new Date(); // current date
let filename = global['gds'].cfg.log.filename(); // filename let filename = cfg.log.filename(); // filename
let dir = global['gds'].cfg.log.directory(); // directory let dir = cfg.log.directory(); // directory
let path = dir + filename; // filepath let path = dir + filename; // filepath
let fs_options = { // fs options for encoding, file mode and file flag let fs_options = { // fs options for encoding, file mode and file flag
encoding: "utf8", encoding: "utf8",
@ -46,7 +47,7 @@ newLine = (prefix, obj) => {
} }
}; };
fallback = (fn, ...data) => { log = (fn, ...data) => {
if(data.length == 1) data = data[0]; if(data.length == 1) data = data[0];
fn.apply(null, data); fn.apply(null, data);
@ -55,8 +56,7 @@ fallback = (fn, ...data) => {
// LOG | INFO // LOG | INFO
methods.log = (...data) => { methods.log = (...data) => {
if(global['modules'].cli) global['modules'].cli.log.apply(global['modules'].cli, data); log(console.log, data);
else fallback(console.log, data);
if(data.length == 1) data = data[0]; if(data.length == 1) data = data[0];
newLine(" [LOG]", data); newLine(" [LOG]", data);
@ -65,8 +65,7 @@ methods.info = methods.log;
// WARNING // WARNING
methods.warn = (...data) => { methods.warn = (...data) => {
if(global['modules'].cli) global['modules'].cli.log.apply(global['modules'].cli, data); log(console.warn, data);
else fallback(console.warn, data);
if(data.length == 1) data = data[0]; if(data.length == 1) data = data[0];
newLine(" [WARN]", data); newLine(" [WARN]", data);
@ -74,8 +73,7 @@ methods.warn = (...data) => {
// ERROR // ERROR
methods.error = (...data) => { methods.error = (...data) => {
if(global['modules'].cli) global['modules'].cli.log.apply(global['modules'].cli, data); log(console.error, data);
else fallback(console.error, data);
if(data.length == 1) data = data[0]; if(data.length == 1) data = data[0];
newLine("[ERROR]", data); newLine("[ERROR]", data);
@ -85,8 +83,7 @@ methods.err = methods.error;
// DEBUG // DEBUG
methods.debug = (...data) => { methods.debug = (...data) => {
if(global['gds'].debug) { if(global['gds'].debug) {
if(global['modules'].cli) global['modules'].cli.log.apply(global['modules'].cli, data); log(console.log, data);
else fallback(console.log, data);
if(data.length == 1) data = data[0]; if(data.length == 1) data = data[0];
newLine("[DEBUG]", data); newLine("[DEBUG]", data);

View File

@ -7,6 +7,8 @@
// init // init
var methods = {}; var methods = {};
var cfg = require(global['__dirname']+'/bin/config');
/** /**
* start web server * start web server
* @author Ruben Meyer * @author Ruben Meyer
@ -33,7 +35,7 @@ methods.start = () => {
// Access Control Headers // Access Control Headers
app.use( (req, res, next) => { app.use( (req, res, next) => {
res.set({ 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-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
@ -50,6 +52,7 @@ methods.start = () => {
// path already cached; not exist // path already cached; not exist
if(global['gds'].cache.web[joined_path] == false) { if(global['gds'].cache.web[joined_path] == false) {
res.status(404).end(); res.status(404).end();
global['logs'].info("[web] (404) path not found: "+joined_path);
// path already cached; exist // path already cached; exist
} else if(global['gds'].cache.web[joined_path] == true){ } else if(global['gds'].cache.web[joined_path] == true){
let contentType = mime.contentType(path.extname(joined_path)); let contentType = mime.contentType(path.extname(joined_path));
@ -66,6 +69,7 @@ methods.start = () => {
fs.createReadStream(joined_path).pipe(res); fs.createReadStream(joined_path).pipe(res);
} else { } else {
res.status(404).end(); res.status(404).end();
global['logs'].info("[web] (404) path not found: "+joined_path);
} }
}); });
} }
@ -76,7 +80,7 @@ methods.start = () => {
app.use(bp.urlencoded({ app.use(bp.urlencoded({
extended: true extended: true
})); }));
app.use(cp(global['gds'].cfg.web.cookieKey)); app.use(cp(cfg.web.cookieKey));
// Pretty print // Pretty print
if(app.get('env') === 'debug') if(app.get('env') === 'debug')
@ -84,7 +88,7 @@ methods.start = () => {
// Sessions // Sessions
session_options = { session_options = {
secret: global['gds'].cfg.web.sessionKey, secret: cfg.web.sessionKey,
resave: false, resave: false,
saveUninitialized: false, cookie: {}}; saveUninitialized: false, cookie: {}};
if(app.get('env') === 'production') { if(app.get('env') === 'production') {
@ -93,13 +97,19 @@ methods.start = () => {
app.use(session_handler(session_options)); app.use(session_handler(session_options));
// web routes // web routes
app.use('/', require(global['__dirname']+'/bin/web/routes/static')); (async function() {
app.use('/api', require(global['__dirname']+'/bin/web/routes/api')); 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 // start server
app.listen(global['gds'].cfg.web.port, () => { app.listen(cfg.web.port, () => {
global['modules'].logs.log("Server is listening on port: "+global['gds'].cfg.web.port); global['logs'].log("Server is listening on port: "+cfg.web.port);
}); });
})();
}; };
module.exports = methods; module.exports = methods;

View File

@ -6,57 +6,64 @@
var express = require('express'); var express = require('express');
var route = express.Router(); var route = express.Router();
asyncer = require('express-async-handler');
/** getRoutes = async () => {
* register a user; currently not implemented let db = global['requireModule']('database');
* @url /register await db.connect();
* @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', (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) { * register a user; currently not implemented
return res.type('json').status(401).end(JSON.stringify({ * @url /register
status: 401, * @method POST
message: [ */
'msg.request.data.missing', route.post('/register', (req, res) => {
'msg.auth.login.failed' // 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 {
let email = req.body.email; // am i rite?
let pass = req.body.password; 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 database error
if(err) { if(user.err) {
// log error while debugging // log error while debugging
global['logs'].debug(err); global['logs'].debug(user.err);
// login failed because of database error // login failed because of database error
return res.type('json').status(500).end(JSON.stringify({ 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 // 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({ return res.type('json').status(401).end(JSON.stringify({
status: 401, status: 401,
message: 'msg.auth.login.failed' message: 'msg.auth.login.failed'
@ -81,8 +88,8 @@ route.post('/login', (req, res) => {
// add session data // add session data
req.session.user = { req.session.user = {
'id': rep[0]._id, 'id': user.reply[0]._id,
'group': rep[0].group 'group': user.reply[0].group
}; };
return res.type('json').end(JSON.stringify({ return res.type('json').end(JSON.stringify({
@ -91,71 +98,72 @@ route.post('/login', (req, res) => {
type: 'form' // TODO: types - { form, access_app} 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 * cancel app request and clear it
* @url /api/authenticate * @url /api/cancel
* @method POST * @method GET
* @POST ['applicationId', 'applicationSecret', 'userId', 'token'] */
* @TODO add implementation route.get('/cancel', (req, res) => {
*/ // if user is logged in
route.post('/authenticate', (req, res) => { if(req.session && req.session.user) {
// TODO: authenticate req.session.appRequest = {};
});
/** return res.type('json').end(JSON.stringify({
* cancel app request and clear it status: 200,
* @url /api/cancel message: 'msg.request.operation.cancel.successful'
* @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({ // user isnt logged in
status: 200, } else {
message: 'msg.request.operation.cancel.successful' 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'
}));
}
});
/**
* 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'
]
})); }));
} }
});
/**
* 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 // database error
if(err) { if(authCode.err) {
global['logs'].debug(err); global['logs'].debug(authCode[1]);
return res.type('json').status(500).end(JSON.stringify({ return res.type('json').status(500).end(JSON.stringify({
status: 500, status: 500,
message: [ message: [
@ -165,25 +173,24 @@ route.get('/redirect', (req, res) => {
} }
else if(rep) { else if(rep) {
// retrieve apps // retrieve apps
global['modules'].database.getApps((err2, rep2) => { apps = await db.getApps();
// database error // database error
if(err2) { if(apps.reply) {
global['logs'].debug(err2); global['logs'].debug(apps.err);
return res.type('json').status(500).end(JSON.stringify({ return res.type('json').status(500).end(JSON.stringify({
status: 500, status: 500,
message: [ message: [
'msg.database.error' '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 { } else {
// database error // database error
@ -194,47 +201,51 @@ route.get('/redirect', (req, res) => {
] ]
})); }));
} }
}); // user isnt logged in
// user isnt logged in } else {
} else { return res.type('json').end(JSON.stringify({
return res.type('json').end(JSON.stringify({ status: 401,
status: 401, message: 'msg.auth.login.required'
message: 'msg.auth.login.required' }));
})); }
} }));
});
/** /**
* logout user * logout user
* @url /api/logout * @url /api/logout
* @method GET * @method GET
*/ */
route.get('/logout', (req, res) => { route.get('/logout', (req, res) => {
// user needs to be logged in // user needs to be logged in
if(!req.session || !req.session.user) { if(!req.session || !req.session.user) {
return res.type('json').end(JSON.stringify({ return res.type('json').end(JSON.stringify({
status: 401, status: 401,
message: 'msg.auth.login.required' message: 'msg.auth.login.required'
})); }));
// logout user // logout user
} else { } else {
res.clearCookie('RememberMe'); res.clearCookie('RememberMe');
req.session.destroy(); req.session.destroy();
return res.type('json').end(JSON.stringify({ return res.type('json').end(JSON.stringify({
status: 200, status: 200,
message: 'msg.auth.logout.successful' 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));
}); });
}
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
};

View File

@ -4,12 +4,16 @@
* (c) Ruben Meyer <contact@rxbn.de> * (c) Ruben Meyer <contact@rxbn.de>
*/ */
var express = require('express'); express = require('express');
var route = express.Router(); route = express.Router();
asyncer = require('express-async-handler');
var fs = require('fs'); fs = require('fs');
var path = require('path'); path = require('path');
// reduce IO file checks - save file state in cache
var fileCheck = (file) => { var fileCheck = (file) => {
if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {}; if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {};
let dir = global['__dirname'] + '/bin/web/views'; let dir = global['__dirname'] + '/bin/web/views';
@ -29,37 +33,41 @@ var fileCheck = (file) => {
} }
}; };
/** let getRoutes = async () => {
* main page let db = global['requireModule']('database');
* @url / await db.connect();
* @method all
*/ /**
route.all('/', function(req, res, next) { * main page
// TODO: show login page or dashboard * @url /
// res.end('login or dashboard'); * @method all
global['modules'].database.getApps((err, rep) => { */
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', { res.render('index', {
session: req.session, session: req.session,
apps: rep apps: apps.reply
}); });
}) }));
});
/** /**
* login page or apprequest page * login page or apprequest page
* @url / * @url /
* @method GET * @method GET
*/ */
route.get('/authenticate', (req, res) => { route.get('/authenticate', asyncer(async (req, res) => {
if(req.session) { if(req.session) {
// if there isnt an apprequest // if there isnt an apprequest
if(!req.session.appRequest) if(!req.session.appRequest)
req.session.appRequest = {}; // TODO: data req.session.appRequest = {}; // TODO: data
} }
// query apps
apps = await db.getApps();
// query apps
global['modules'].database.getApps((err, rep) => {
// set appId in appRequest // set appId in appRequest
if(req.query.appId) { if(req.query.appId) {
if(req.query.appId && typeof req.query.appId == "string") { if(req.query.appId && typeof req.query.appId == "string") {
@ -77,7 +85,7 @@ route.get('/authenticate', (req, res) => {
res.render('request', { res.render('request', {
session: req.session, session: req.session,
appRequest: req.session.appRequest, appRequest: req.session.appRequest,
apps: rep apps: apps.reply
}); });
// if user isnt logged in, show login page // if user isnt logged in, show login page
} else { } else {
@ -85,7 +93,7 @@ route.get('/authenticate', (req, res) => {
let view_obj = { session: req.session }; let view_obj = { session: req.session };
if(req.query.appId) { if(req.query.appId) {
rep.forEach((app) => { apps.reply.forEach((app) => {
if(app._id == req.query.appId) if(app._id == req.query.appId)
view_obj["login_title"] = "Login to use "+app.name+" via authRxbn"; // appRequest app name 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); res.render('login', view_obj);
} }
}); }));
});
/** /**
* all other routes * all other routes
* @url /* * @url /*
* @method all * @method all
* @TODO comments * @TODO comments
*/ */
route.all('/*', (req, res, next) => { route.all('/*', (req, res, next) => {
// passthrough to next route // passthrough to next route
if(req.path.startsWith('/api')) if(req.path.startsWith('/api'))
return next(); 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"; let group = "anon";
if(req.session && req.session.user) { if(req.session && req.session.user) {
group = "user"; group = "user";
if(req.session.user.group == 999) group = "admin"; if(req.session.user.group == 999) group = "admin";
} }
pathRules.forEach((rule) => { pathRules.forEach((rule) => {
if(rule.rule == "block") { if(rule.rule == "block") {
if(group == rule.group) { if(group == rule.group) {
let regex = new RegExp(rule.expression, "g"); let regex = new RegExp(rule.expression, "g");
if(regex.test(req.path)) { if(regex.test(req.path)) {
if(rule.type == "404") { if(rule.type == "404") {
return res.status(404).render('error/404', { global['logs'].info("[web] (404) path not found: "+req.path);
error_code: 404, return res.status(404).render('error/404', {
error_msg: 'msg.request.file.not_found', error_code: 404,
session: req.session error_msg: 'msg.request.file.not_found',
}); session: req.session
} else if(rule.type == "missing_permission") { });
return res.status(401).render('error/permission', { } else if(rule.type == "missing_permission") {
error_code: 401, return res.status(401).render('error/permission', {
session: req.session error_code: 401,
}); session: req.session
} else if(rule.type == "login") { });
return res.status(401).render('error/login', { } else if(rule.type == "login") {
error_code: 401, return res.status(401).render('error/login', {
session: req.session error_code: 401,
}); session: req.session
} else { });
return res.status(401).render('error/error', { } else {
error_code: 401, return res.status(401).render('error/error', {
session: req.session 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 route;
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
});
}
// TODO: try to login module.exports = {
// TODO: role-based authorization getRoutes: getRoutes
// TODO: show login page or page };
});
module.exports = route;

View File

@ -7,13 +7,12 @@
"license": "", "license": "",
"dependencies": { "dependencies": {
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"chalk": "^2.4.2", "cookie-parser": "^1.4.5",
"cookie-parser": "^1.4.4",
"express": "^4.17.1", "express": "^4.17.1",
"express-session": "^1.16.1", "express-async-handler": "^1.1.4",
"mongo-sanitize": "^1.0.1", "express-session": "^1.17.1",
"mongoose": "^5.5.12", "mongo-sanitize": "^1.1.0",
"pug": "^2.0.3", "mongoose": "^5.9.28",
"vorpal": "^1.12.0" "pug": "^3.0.0"
} }
} }