1
0
Fork 0

Compare commits

...

6 Commits

11 changed files with 329 additions and 172 deletions

1
app.js
View File

@ -8,7 +8,6 @@
// 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,
cache: {},
cfg: require(__dirname+'/bin/config') cfg: require(__dirname+'/bin/config')
}; };
global['__dirname'] = __dirname; global['__dirname'] = __dirname;

View File

@ -28,7 +28,8 @@ 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)
name: 'authRXBN', name: 'authRXBN',
passhashDelimiter: '|' passhashDelimiter: '|',
adminGroupname: "Administration"
}, },
mongoose: { mongoose: {
uri: process.env.DB_URL, uri: process.env.DB_URL,

View File

@ -34,12 +34,17 @@ models.user = new Schema({
}, },
mfa: { // multi factor authentication mfa: { // multi factor authentication
active: {type: Boolean, default: false}, active: {type: Boolean, default: false},
type: {type: String, default: ""}, data: {type: Array, default: [ // add each mfa type
data: {type: String, default: ""} // tel number or secret token //{
// no: 0,
// type: "TOTP"||"HOTP"||"WebAuthn",
// data: "32CharHex"||"32CharHex"||"UserPublicKey"
//}, ...
]}
}, },
settings: {type: Object, default: {}}, // custom settings (theme etc. pp.) settings: {type: Object, default: {}}, // custom settings (theme etc. pp.)
roles: {type: String, default: ""}, // user-defined roles and permissions roles: {type: String, default: ""}, // user-defined roles and permissions
group: {type: Number, default: 0}, // group-id for group-defined roles and permissions group: Schema.Types.ObjectId, // reference to group
reg_date: {type: Date, default: Date.now}, // registration date reg_date: {type: Date, default: Date.now}, // registration date
last_action: {type: Date, default: Date.now}, // last action (activity date) last_action: {type: Date, default: Date.now}, // last action (activity date)
}); });
@ -51,6 +56,15 @@ models.group = new Schema({
roles: {type: String, default: ""} // roles; separated by commas "a,b,a.b,c.*,d.z.*" roles: {type: String, default: ""} // roles; separated by commas "a,b,a.b,c.*,d.z.*"
}); });
// pathRules for access management
models.pathRules = new Schema({
group: Schema.Types.ObjectId, // reference to group
expression: String, // path expression; e.g.: "(/blocks/.*)"
rule: String, // e.g.: block
type: String, // e.g: "404", "missing_permission", "login",
options: {type: Object, default: {}} // more options...
});
// application | service // application | service
models.application = new Schema({ models.application = new Schema({
name: String, // recognizable application name; ex. "passRXBN - Password Manager" name: String, // recognizable application name; ex. "passRXBN - Password Manager"
@ -81,6 +95,7 @@ module.exports = (con) => {
// initialize models // initialize models
mdls.user = con.model('User', models.user); mdls.user = con.model('User', models.user);
mdls.group = con.model('Group', models.group); mdls.group = con.model('Group', models.group);
mdls.pathRules = con.model('PathRules', models.pathRules);
mdls.application = con.model('Application', models.application); mdls.application = con.model('Application', models.application);
mdls.activity = con.model('Activity', models.activity); mdls.activity = con.model('Activity', models.activity);
mdls.authCode = con.model('AuthCode', models.authCode); mdls.authCode = con.model('AuthCode', models.authCode);

View File

@ -73,8 +73,8 @@ methods.getConnection = () => {
* @param {String} nick nickname * @param {String} nick nickname
* @param {String} email email * @param {String} email email
* @param {String} passhash hashed password * @param {String} passhash hashed password
* @param {Number} group Group id (normally 0 -> user) * @param {Number} group Group ObjectId
* @return {Array} async (reply, err) * @return {Object} async (reply, err)
*/ */
methods.addUser = async (nick, email, passhash, group) => { 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 nick !== 'string') return {err: new TypeError('nick is not a string::database.addUser('+nick+','+email+','+passhash+','+group+')', module.filename)};
@ -105,7 +105,7 @@ methods.addUser = async (nick, email, passhash, group) => {
* @async * @async
* @TODO add functionality * @TODO add functionality
* @param {String} haystack email or nick * @param {String} haystack email or nick
* @return {Array} async(reply, err) * @return {Object} async(reply, err)
*/ */
methods.delUser = async (haystack) => { methods.delUser = async (haystack) => {
if(typeof haystack !== 'string') return {err: new TypeError('haystack is not a string::database.delUser('+haystack+')', module.filename)}; if(typeof haystack !== 'string') return {err: new TypeError('haystack is not a string::database.delUser('+haystack+')', module.filename)};
@ -129,7 +129,7 @@ methods.delUser = async (haystack) => {
* get all users * get all users
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @return {Array} async(reply, err) * @return {Object} async(reply, err)
*/ */
methods.getUsers = async () => { methods.getUsers = async () => {
let userModel = models.user; let userModel = models.user;
@ -150,10 +150,10 @@ methods.getUsers = async () => {
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @param {String|String[]} haystack email or nick * @param {String|String[]} haystack email or nick
* @return async(reply, err) * @return {Object} async(reply, err)
*/ */
methods.getUser = async (haystack) => { 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)}; 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; let userModel = models.user;
@ -161,14 +161,17 @@ methods.getUser = async (haystack) => {
haystack = sanitize(haystack); haystack = sanitize(haystack);
let or = []; let or = [];
if(typeof haystack === 'string') { if(haystack instanceof mongoose.Types.ObjectId) {
or.push({_id: haystack});
}
else if(typeof haystack === 'string') {
or = [{nickname: haystack}, {email: haystack}, {token: haystack}]; or = [{nickname: haystack}, {email: haystack}, {token: haystack}];
if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: haystack}); if(haystack.match(/^[0-9a-fA-F]{24}$/)) or.push({_id: mongoose.Types.ObjectId(haystack)});
} }
else { else {
or = []; or = [];
for(let i = 0; i < haystack.length; i++) { for(let i = 0; i < haystack.length; i++) {
if(haystack[i].match(/^[0-9a-fA-F]{24}$/)) or.push({_id: haystack[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({nickname: haystack[i]});
or.push({email: haystack[i]}); or.push({email: haystack[i]});
or.push({token: haystack[i]}); or.push({token: haystack[i]});
@ -178,8 +181,8 @@ methods.getUser = async (haystack) => {
try { try {
users = await userModel.find().or(or).exec(); users = await userModel.find().or(or).exec();
if(users.length > 0) if(users.length == 1)
return {reply: users}; return {reply: users[0]};
else else
return {reply: false}; return {reply: false};
} catch(err) { } catch(err) {
@ -194,17 +197,17 @@ methods.getUser = async (haystack) => {
* @async * @async
* @param {Number} id User ID * @param {Number} id User ID
* @param {Object} obj data * @param {Object} obj data
* @return {Array} async(reply, err) * @return {Object} async(reply, err)
*/ */
methods.updateUser = async (id, obj) => { 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 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)}; 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; let userModel = models.user;
try { try {
data = await userModel.findByIdAndUpdate(id, obj).exec(); data = await userModel.findByIdAndUpdate(id, obj).exec();
return {data: data}; return {reply: data};
} catch(err) { } catch(err) {
return {err: err}; return {err: err};
} }
@ -219,10 +222,10 @@ methods.updateUser = async (id, obj) => {
* @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
* @return {Array} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err) * @return {Object} async({date => 'Login Date', token => 'RememberMe Cookie Token'}, err)
*/ */
methods.addActivity = async (id, data) => { 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 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)}; 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 date = new Date().toISOString();
@ -260,6 +263,204 @@ methods.addActivity = async (id, data) => {
} }
}; };
// //////// /////// //////// // // /////// //////
// // // // // // // // // // //
// //////// /////// // // // // /////// //////
// // // // // // // // // // //
// //////// // // //////// //////// // //////
//
/////////////////////////////////////////////////////////////////
/**
* 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};
}
};
// //////// //////// //////// ////// // //////// //////// //////// //////
// // // // // // // // // // // // // // // //
@ -269,11 +470,12 @@ methods.addActivity = async (id, data) => {
// //
////////////////////////////////////////////// //////////////////////////////////////////////
/** /**
* get Applications * get Applications
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @return {Array} async(apps, err) * @return {Object} async(apps, err)
*/ */
methods.getApps = async () => { methods.getApps = async () => {
var Application = models.application; var Application = models.application;
@ -289,8 +491,9 @@ methods.getApps = async () => {
* return auth obj * return auth obj
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @TODO
* @param {Object} obj data obj (aId, uId) * @param {Object} obj data obj (aId, uId)
* @return {Array} async({timestamp, token}, err) * @return {Object} async({timestamp, token}, err)
*/ */
methods.setAuthCode = async (obj) => { methods.setAuthCode = async (obj) => {
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 {err: new TypeError('obj is not an object::database.setAuthCode('+JSON.stringify(obj)+')', module.filename)};
@ -322,8 +525,9 @@ methods.setAuthCode = async (obj) => {
* return auth obj * return auth obj
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @TODO
* @param {Object} obj data obj (aId, aSecret, uId, token) * @param {Object} obj data obj (aId, aSecret, uId, token)
* @return {Array} async(bool, err) * @return {Object} async(bool, err)
*/ */
methods.getAuth = async (obj) => { methods.getAuth = async (obj) => {
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 {err: new TypeError('obj is not an object::database.getAuthCode('+JSON.stringify(obj)+')', module.filename)};
@ -344,9 +548,11 @@ methods.getAuth = async (obj) => {
var Application = models.application; var Application = models.application;
try { try {
if(!(obj.aId instanceof mongoose.Types.ObjectId)) obj.aId = mongoose.Types.ObjectId(obj.aId);
data1 = await Application.findOne({ data1 = await Application.findOne({
$and: [ $and: [
{_id: mongoose.Types.ObjectId(obj.aId)}, {_id: obj.aId},
{secret: obj.aSecret} {secret: obj.aSecret}
] ]
}).exec(); }).exec();
@ -373,11 +579,12 @@ methods.getAuth = async (obj) => {
}; };
/** /**
* return if app is permitted to do access call * return app permission
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @TODO
* @param {Object} obj data obj (aId, redirectUrl) * @param {Object} obj data obj (aId, redirectUrl)
* @return {Array} async(bool, err) * @return {Object} async(bool, err)
*/ */
methods.verifyAppCall = async (obj) => { methods.verifyAppCall = async (obj) => {
return {}; return {};
@ -395,7 +602,7 @@ methods.verifyAppCall = async (obj) => {
* returns user count * returns user count
* @author Ruben Meyer * @author Ruben Meyer
* @async * @async
* @return {Array} async(int, err) * @return {Object} async(int, err)
*/ */
methods.userCount = async () => { methods.userCount = async () => {
let userModel = models.user; let userModel = models.user;

View File

@ -45,35 +45,21 @@ methods.start = () => {
//static files //static files
app.use('/res', (req, res, next) => { app.use('/res', (req, res, next) => {
if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {};
let dir = global['__dirname'] + '/res/web'; let dir = global['__dirname'] + '/res/web';
let joined_path = path.join(dir, /^[^?]+/.exec(req.url)[0]); let joined_path = path.join(dir, /^[^?]+/.exec(req.url)[0]);
// path already cached; not exist fs.exists(joined_path, (exists) => {
if(global['gds'].cache.web[joined_path] == false) { if(exists) {
res.status(404).end(); let contentType = mime.contentType(path.extname(joined_path));
global['logs'].info("[web] (404) path not found: "+joined_path); res.setHeader('Content-Type', contentType);
// path already cached; exist
} else if(global['gds'].cache.web[joined_path] == true){
let contentType = mime.contentType(path.extname(joined_path));
res.setHeader('Content-Type', contentType);
fs.createReadStream(joined_path).pipe(res);
// check path
} else {
fs.exists(joined_path, (exists) => {
global['gds'].cache.web[joined_path] = exists;
if(exists) {
let contentType = mime.contentType(path.extname(joined_path));
res.setHeader('Content-Type', contentType);
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); global['logs'].info("[web] (404) path not found: "+joined_path);
} }
}); });
}
}); });
// BodyParser & CookieParser // BodyParser & CookieParser

View File

@ -76,7 +76,7 @@ getRoutes = async () => {
} }
// no reply (user does not exist) or password is wrong // no reply (user does not exist) or password is wrong
if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1 || !global['requireModule']('auth').validateHash(user.reply[0].passhash, pass)) { if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1 || !global['requireModule']('auth').validateHash(user.reply.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'
@ -88,8 +88,8 @@ getRoutes = async () => {
// add session data // add session data
req.session.user = { req.session.user = {
'id': user.reply[0]._id, 'id': user.reply._id,
'group': user.reply[0].group 'group': user.reply.group
}; };
return res.type('json').end(JSON.stringify({ return res.type('json').end(JSON.stringify({
@ -233,16 +233,6 @@ getRoutes = async () => {
} }
}); });
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; return route;
}; };

View File

@ -7,11 +7,6 @@
/** /**
* EXPLANATIONS: * EXPLANATIONS:
* *
* groups: ["anon", "user", "admin"]
* - anon: not logged in; no cookies
* - user: logged in; non-special group
* - admin: logged in; admin group 999 or equivalent
*
* expressions: RegExp tested on req.path * expressions: RegExp tested on req.path
* - ex.: * - ex.:
* - req.path = "/profile/456"; * - req.path = "/profile/456";
@ -25,44 +20,8 @@
* - 404: File not found * - 404: File not found
* - missing_permission: Missing Permission page * - missing_permission: Missing Permission page
* - login: login page * - login: login page
*
* NOW ADDED TO DATABASE
*/ */
let rules = [
{
group: "anon",
expression: "(/blocks/.*)",
rule: "block",
type: "404"
},
{
group: "anon",
expression: "(/error/.*)",
rule: "block",
type: "404"
},
{
group: "anon",
expression: "(/admin/.*)",
rule: "block",
type: "login"
},
{
group: "user",
expression: "(/blocks/.*)",
rule: "block",
type: "404"
},
{
group: "user",
expression: "(/error/.*)",
rule: "block",
type: "404"
},
{
group: "user",
expression: "(/admin/.*)",
rule: "block",
type: "missing_permission"
}
];
module.exports = rules; module.exports = [];

View File

@ -13,26 +13,6 @@ path = require('path');
var cfg = require(global['__dirname']+'/bin/config'); var cfg = require(global['__dirname']+'/bin/config');
// reduce IO file checks - save file state in cache
var fileCheck = (file) => {
if(typeof global['gds'].cache.web == 'undefined') global['gds'].cache.web = {};
let dir = global['__dirname'] + '/bin/web/views';
let path_j = path.join(dir, file.toLowerCase());
if(typeof global['gds'].cache.web[path_j] == 'undefined') {
if(fs.existsSync(path_j+'.pug')) {
global['gds'].cache.web[path_j] = true;
} else {
global['gds'].cache.web[path_j] = false;
}
}
if(global['gds'].cache.web[path_j] === true) {
return path_j;
} else {
return false;
}
};
let getRoutes = async () => { let getRoutes = async () => {
let db = global['requireModule']('database'); let db = global['requireModule']('database');
await db.connect(); await db.connect();
@ -40,22 +20,27 @@ let getRoutes = async () => {
/** /**
* main page * main page
* @url / * @url /
* @method all * @method GET
*/ */
route.all('/', asyncer(async (req, res, next) => { route.get('/', asyncer(async (req, res, next) => {
// TODO: show login page or dashboard obj = {
// res.end('login or dashboard');
apps = await db.getApps();
res.render('index', {
session: req.session, session: req.session,
apps: apps.reply,
cfg: cfg cfg: cfg
}); };
// if user is logged in
if(req.session && req.session.user) {
obj.user = (await db.getUser(req.session.user.id)).reply;
obj.group = (await db.getGroup(obj.user.group)).reply;
apps = await db.getApps();
obj.apps = apps.reply;
}
res.render('index', obj);
})); }));
/** /**
* login page or apprequest page * login page or apprequest page
* @url / * @url /authenticate
* @method GET * @method GET
*/ */
route.get('/authenticate', asyncer(async (req, res) => { route.get('/authenticate', asyncer(async (req, res) => {
@ -75,25 +60,29 @@ let getRoutes = async () => {
// req.query.appId // req.query.appId
// verify appId (if in rep) // verify appId (if in rep)
req.session.appRequest.appId = req.query.appId; req.session.appRequest.appId = req.query.appId;
// TODO: on accept, setAuthCode and redirect with token
// on cancel, redirect to dashboard
} }
} else {
return res.redirect('/');
} }
// if user is logged in, show request page // if user is logged in, show request page
if(req.session && req.session.user) { if(req.session && req.session.user) {
res.render('request', { user = await db.getUser(req.session.user.id);
group = await db.getGroup(user.reply.group);
return res.render('request', {
session: req.session, session: req.session,
appRequest: req.session.appRequest, appRequest: req.session.appRequest,
apps: apps.reply, apps: apps.reply,
cfg: cfg cfg: cfg,
user: user.reply,
group: group.reply
}); });
// if user isnt logged in, show login page // if user isnt logged in, show login page
} else { } else {
if(!req.query.appId) req.session.appRequest = {}; if(!req.query.appId) req.session.appRequest = {};
let view_obj = { session: req.session }; let view_obj = { session: req.session, cfg: cfg };
if(req.query.appId) { if(req.query.appId) {
apps.reply.forEach((app) => { apps.reply.forEach((app) => {
if(app._id == req.query.appId) if(app._id == req.query.appId)
@ -102,7 +91,10 @@ let getRoutes = async () => {
} }
res.render('login', view_obj); return res.render('login', view_obj);
}
}));
} }
})); }));
@ -110,30 +102,36 @@ let getRoutes = async () => {
* all other routes * all other routes
* @url /* * @url /*
* @method all * @method all
* @TODO comments
*/ */
route.all('/*', asyncer(async (req, res, next) => { route.get('/*', asyncer(async (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 = await db.getPathRules();
let group = "anon"; // retrieve guest group - set as default
let groups = await db.getGroups();
guestId = null;
groups.reply.forEach((group) => {
if(group.name == "Guest") guestId = group._id;
});
let group = guestId;
// set user group
if(req.session && req.session.user) { if(req.session && req.session.user) {
group = "user"; group = req.session.user.group;
if(req.session.user.group == 999) group = "admin";
} }
pathRules.forEach((rule) => { for(i = 0; i < pathRules.reply.length; i++) {
rule = pathRules.reply[i];
if(rule.rule == "block") { if(rule.rule == "block") {
if(group == rule.group) { if(group == String(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") {
global['logs'].info("[web] (404) path not found: "+req.path);
return res.status(404).render('error/404', { return res.status(404).render('error/404', {
error_code: 404, error_code: 404,
error_msg: 'msg.request.file.not_found', error_msg: 'msg.request.file.not_found',
@ -146,7 +144,7 @@ let getRoutes = async () => {
session: req.session, session: req.session,
cfg: cfg cfg: cfg
}); });
} else if(rule.type == "login") { } else if(rule.type == "login" && (!req.session || !req.session.user)) {
return res.status(401).render('error/login', { return res.status(401).render('error/login', {
error_code: 401, error_code: 401,
session: req.session, session: req.session,
@ -162,15 +160,13 @@ let getRoutes = async () => {
} }
} }
} }
}); };
if(fileCheck(req.path)) {
// query apps
apps = await db.getApps();
let dir = global['__dirname'] + '/bin/web/views';
let path_j = path.join(dir, req.path.toLowerCase());
if(fs.existsSync(path_j+'.pug')) {
return res.render(req.path.replace(/^\//, ''), { return res.render(req.path.replace(/^\//, ''), {
session: req.session, session: req.session,
apps: apps.reply,
cfg: cfg cfg: cfg
}); });
} else { } else {
@ -182,10 +178,6 @@ let getRoutes = async () => {
cfg: cfg cfg: cfg
}); });
} }
// TODO: try to login
// TODO: role-based authorization
// TODO: show login page or page
})); }));
return route; return route;

View File

@ -7,7 +7,10 @@ append var
div(class="uk-width-auto uk-width-1-4@s") div(class="uk-width-auto uk-width-1-4@s")
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right .uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
h1 Please login h1 Please login
form.uk-form-horizontal #login_msg.uk-alert(data-uk-alert).uk-hidden
a.uk-close-alt.uk-alert-close(href="#")
p
form.uk-form-horizontal(onsubmit="return loginEvent();")
.uk-margin .uk-margin
label.uk-form-label(for="login_user") Username / Email label.uk-form-label(for="login_user") Username / Email
.uk-form-controls .uk-form-controls
@ -16,5 +19,6 @@ append var
label.uk-form-label(for="login_pass") Password label.uk-form-label(for="login_pass") Password
.uk-form-controls .uk-form-controls
input.uk-input#login_pass(type="password") input.uk-input#login_pass(type="password")
a(href="/login").uk-button.uk-button-default Login input(hidden,type="submit")
button(onclick="login()").uk-button.uk-button-default Login
div(class="uk-width-auto uk-width-1-4@s") div(class="uk-width-auto uk-width-1-4@s")

View File

@ -1,7 +1,8 @@
mixin navItem(name, id, symbol, href) mixin navItem(name, id, symbol, href)
li(title=name, id=id) li(title=name, id=id)
a(href=href) a(href=href)
i.fa-fw(class=symbol) if(symbol !== "")
i.fa-fw(class=symbol)
span= name span= name
// Navigation // Navigation
@ -14,6 +15,8 @@ nav(uk-navbar).uk-navbar-container
.uk-navbar-right.uk-margin-right .uk-navbar-right.uk-margin-right
ul.uk-navbar-nav ul.uk-navbar-nav
if(session && session.user) if(session && session.user)
if(group && group.name == cfg.app.adminGroupname)
+navItem("Administration", "admin", "", "/admin")
+navItem("Apps", "apps", "fas fa-tachometer-alt", "/") +navItem("Apps", "apps", "fas fa-tachometer-alt", "/")
+navItem("Settings", "settings", "fas fa-wrench", "/settings") +navItem("Settings", "settings", "fas fa-wrench", "/settings")
+navItem("Logout", "logout", "fas fa-sign-out-alt", "/logout") +navItem("Logout", "logout", "fas fa-sign-out-alt", "/logout")

View File

@ -1,7 +1,6 @@
function loginEvent() {
login(); // '/login', login user
return false; function loginEvent() { login(); return false; } // btnEvent handling
}
function login() { function login() {
let user = document.getElementById("login_user").value; let user = document.getElementById("login_user").value;
let pass = document.getElementById("login_pass").value; let pass = document.getElementById("login_pass").value;
@ -42,6 +41,7 @@ function login() {
}; };
}; };
// '/logout', logout user
function logout() { function logout() {
let ajax = new XMLHttpRequest(); let ajax = new XMLHttpRequest();
ajax.open("GET", "/api/logout", true); ajax.open("GET", "/api/logout", true);
@ -58,6 +58,7 @@ setTimeout(function () {
} }
}, 100); }, 100);
// '/authenticate', cancels Authentication
function cancelRequest() { function cancelRequest() {
let ajax = new XMLHttpRequest(); let ajax = new XMLHttpRequest();
ajax.open("GET", "/api/cancel", true); ajax.open("GET", "/api/cancel", true);