routes/users.js (303 lines of code) (raw):
// Copyright (c) 2017 Chandan B N. All rights reserved.
// user management.
const express = require('express');
const protected = express.Router();
const public = express.Router();
const crypto = require('crypto');
const passport = require('passport');
const pbkdf2 = require('../lib/pbkdf2.js');
const User = require('../models/user');
const conf = require('../config/conf');
const csurf = require('csurf');
const {
matchedData,
check,
validationResult
} = require('express-validator');
const validator = require('validator');
var csrfProtection = csurf();
// If admin allow edits, otherwise display user
protected.get('/profile/:id(' + conf.usernameRegex + ')?', csrfProtection, function (req, res) {
var admin = false;
if (req.user.priv == 0) {
admin = true;
}
if (req.params.id) {
User.findOne({
username: req.params.id
}, function (err, user) {
if (user) {
//if Admin or self then present edit form
if (admin || req.user.username == req.params.id) {
res.render('users/edit', {
title: 'Update profile: ' + user.username,
profile: user,
admin: admin,
page: 'users',
csrfToken: req.csrfToken()
});
} else {
res.render('users/view', {
title: 'Profile: ' + user.username,
profile: user,
admin: admin,
page: 'users',
csrfToken: req.csrfToken()
});
}
} else {
req.flash('error', 'User id not found');
if (admin) {
res.redirect('/users/profile');
} else {
res.render('blank');
}
}
});
} else {
if (admin) {
//new user form
res.render('users/edit', {
title: 'Add new user',
profile: {},
page: 'users',
admin: admin,
csrfToken: req.csrfToken()
});
} else {
req.flash('error', 'Only administrators can add new users');
res.render('blank');
}
}
});
// Register or update an user
protected.post('/profile/:id(' + conf.usernameRegex + ')?', csrfProtection, [
check('name')
.trim()
.isLength({
min: 2,
max: undefined
})
.withMessage('Name too short')
.isLength({
min: 0,
max: 64
})
.withMessage('Name too long'),
check('emoji')
.trim()
.isLength({
min: 0,
max: 8
})
.withMessage('Long Emoji strings are not allowed'),
check('email')
.trim()
.isEmail()
.normalizeEmail()
.isLength({
min: 2,
max: 256
})
.withMessage('User email is invalid'),
check('password')
.custom((value, {
req
}) => value === req.body.password2)
.withMessage('Passwords do not match'),
check('group')
.trim()
.normalizeEmail()
.custom((value, {
req
}) => {
// validate group only if it is a privileged user
// group email from unprivileged users will be ignored
if ((req.user.priv != 0) || validator.isEmail(value)) {
return true;
}
return false;
})
.withMessage('Group email is invalid'),
check('username')
.trim()
.custom((value, {
req
}) => {
// validate username if it is a privileged user
// username from unprivileged users will be ignored
if ((req.user.priv != 0) || validator.matches(value, /^[a-zA-Z0-9]{3,128}$/)) {
return true;
}
req.body.username = "";
return false;
})
.withMessage('Username is invalid'),
check('username')
.custom((value, {
req
}) => {
return User.findOne({
username: value
}).then((user) => {
if ((!req.params.id || req.params.id != value) && user) {
throw new Error('this username is already in use');
return false;
} else {
return true;
}
});
}),
check('priv')
.custom((value, {
req
}) => {
// validate username if it is a privileged user
// username from unprivileged users will be ignored
if ((req.user.priv != 0) || validator.isIn(value, [0, 1, 2])) {
return true;
}
return false;
})
.withMessage('Privilege provided is invalid')
], function (req, res) {
if (req.isAuthenticated()) {
var admin = false;
if (req.user.priv == 0) {
admin = true;
}
let errors = validationResult(req);
let updates = matchedData(req);
if (!errors.isEmpty()) {
for (var e of errors.array()) {
req.flash('error', 'Error: ' + e.msg);
}
// todo, clear invalid username or change form action uri
res.render('users/edit', {
title: 'User ' + req.body.username,
profile: req.body,
admin: admin,
page: 'users',
csrfToken: req.csrfToken()
});
//res.redirect('/users/profile/'+req.user.username);
} else {
if (!admin) {
updates.username = req.user.username;
updates.priv = req.user.priv;
updates.group = req.user.group;
}
let query = {
username: updates.username
};
let updateOptions = {
upsert: true,
setDefaultsOnInsert: true
};
var updateResponse = function (err, doc) {
if (err) {
req.flash('error', err);
res.redirect('/users/profile/' + updates.username);
} else {
var msg = 'New user ' + updates.username + ' created';
if (doc) {
msg = 'Updated ' + updates.username;
}
req.flash('success', msg);
res.redirect('/users/profile/' + updates.username);
}
};
if (updates.password) {
pbkdf2.hash(updates.password, function (err, hash) {
if (err) {
console.error(err);
}
updates.password = hash;
User.findOneAndUpdate(query, updates, updateOptions, updateResponse);
});
} else {
delete updates.password;
User.findOneAndUpdate(query, updates, updateOptions, updateResponse);
}
}
} else {
req.flash('error', 'Aunthentication required!');
res.redirect('/users/login');
}
});
protected.get('/delete/:id(' + conf.usernameRegex + ')', csrfProtection, function (req, res) {
req.flash('warning', 'Deleteing users is not yet implemented. Fow now users can be deleted in the backend database.');
res.render('blank');
});
// Login form
public.get('/login', csrfProtection, function (req, res) {
res.render('users/login', {
title: 'Vulnogram',
csrfToken: req.csrfToken()
});
});
// Login process
public.post('/login', csrfProtection, function (req, res, next) {
passport.authenticate('local', {
successRedirect: req.session.returnTo || '/cve',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
// Logout form
public.get('/logout', function (req, res) {
req.logout(function(err){
if(err) {
return next(err);
}
req.session.returnTo = null;
req.flash('success', 'You are logged out');
res.redirect('/users/login');
});
});
//List users
protected.get('/list', function (req, res) {
if (req.isAuthenticated()) {
User.find({}, [], {
sort: {
_id: 1
}
}, function (err, users) {
if (err) {
res.status(500).send('Error');
} else {
res.render('users/index', {
users: users,
page: 'users'
});
}
});
} else {
}
});
protected.get('/list/json', function (req, res) {
if (req.isAuthenticated()) {
User.find({group:req.user.group}, ['username','name','emoji'], {
sort: {
username: 1
}
}, function (err, users) {
if (err) {
res.status(500).send('Error');
} else {
res.json({
default: req.user.username,
enum: users.map(function(u) { return u.username;}),
options: {enum_titles: users.map(function(u){return u.name})
}});
}
});
} else {
}
});
protected.get('/list/css', function (req, res) {
if (req.isAuthenticated()) {
User.find({group:req.user.group}, ['username','name','emoji'], {
sort: {
username: 1
}
}, function (err, users) {
if (err) {
res.status(500).send('Error');
} else {
res.setHeader('Content-Type', 'text/css');
for(u of users) {
res.write('input[value="'+u.username+'"] + .lbl:before, #vgListTable span[title="'+u.username+'"]:before, .vguser[title="'+u.username+'"]:before {content: "' + u.emoji + ' ";}\n');
}
res.end();
}
});
} else {
}
});
module.exports = {
public: public,
protected: protected
};