1
0
Fork 0

web,db - user groups using object ids now

This commit is contained in:
Ruben Meyer 2020-08-31 09:49:24 +02:00
parent 8ddd2e9013
commit 50cc5c93a9
Signed by: rxbn_
GPG Key ID: BE3BF898BE352FE2
6 changed files with 246 additions and 43 deletions

View File

@ -28,7 +28,8 @@ module.exports = {
app: {
locale: 'de-DE', // default locale (de-DE & en-EN should be available)
name: 'authRXBN',
passhashDelimiter: '|'
passhashDelimiter: '|',
adminGroupname: "Administration"
},
mongoose: {
uri: process.env.DB_URL,

View File

@ -34,12 +34,17 @@ models.user = new Schema({
},
mfa: { // multi factor authentication
active: {type: Boolean, default: false},
type: {type: String, default: ""},
data: {type: String, default: ""} // tel number or secret token
data: {type: Array, default: [ // add each mfa type
//{
// no: 0,
// type: "TOTP"||"HOTP"||"WebAuthn",
// data: "32CharHex"||"32CharHex"||"UserPublicKey"
//}, ...
]}
},
settings: {type: Object, default: {}}, // custom settings (theme etc. pp.)
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
last_action: {type: Date, default: Date.now}, // last action (activity date)
});

View File

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

View File

@ -76,7 +76,7 @@ getRoutes = async () => {
}
// 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({
status: 401,
message: 'msg.auth.login.failed'
@ -88,8 +88,8 @@ getRoutes = async () => {
// add session data
req.session.user = {
'id': user.reply[0]._id,
'group': user.reply[0].group
'id': user.reply._id,
'group': user.reply.group
};
return res.type('json').end(JSON.stringify({

View File

@ -20,22 +20,27 @@ let getRoutes = async () => {
/**
* main page
* @url /
* @method all
* @method GET
*/
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', {
route.get('/', asyncer(async (req, res, next) => {
obj = {
session: req.session,
apps: apps.reply,
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
* @url /
* @url /authenticate
* @method GET
*/
route.get('/authenticate', asyncer(async (req, res) => {
@ -55,25 +60,29 @@ let getRoutes = async () => {
// req.query.appId
// verify appId (if in rep)
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(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,
appRequest: req.session.appRequest,
apps: apps.reply,
cfg: cfg
cfg: cfg,
user: user.reply,
group: group.reply
});
// if user isnt logged in, show login page
} else {
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) {
apps.reply.forEach((app) => {
if(app._id == req.query.appId)
@ -82,7 +91,10 @@ let getRoutes = async () => {
}
res.render('login', view_obj);
return res.render('login', view_obj);
}
}));
}
}));

View File

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