185 lines
6.7 KiB
TypeScript
185 lines
6.7 KiB
TypeScript
import express from 'express';
|
|
import mongoose from 'mongoose';
|
|
import bcrypt from 'bcryptjs';
|
|
import _ from 'lodash';
|
|
|
|
import UserValidate from './validate/user';
|
|
import UserModel from '../models/user';
|
|
import Mail from '../helpers/mail';
|
|
import res400 from './validate/res400';
|
|
import db from '../db';
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
router.get('/users', (req, res) => {
|
|
if (!req.auth(res, ['admin'], 'basic')) return;
|
|
|
|
UserModel.find({}).lean().exec( (err, data:any) => {
|
|
// validate all and filter null values from validation errors
|
|
res.json(_.compact(data.map(e => UserValidate.output(e))));
|
|
});
|
|
});
|
|
|
|
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
|
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
|
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
|
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
|
|
|
const username = getUsername(req, res);
|
|
if (!username) return;
|
|
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
|
|
if (err) return next(err);
|
|
if (data) {
|
|
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
|
|
}
|
|
else {
|
|
res.status(404).json({status: 'Not found'});
|
|
}
|
|
});
|
|
});
|
|
|
|
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
|
router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {
|
|
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
|
|
|
const username = getUsername(req, res);
|
|
if (!username) return;
|
|
|
|
const {error, value: user} = UserValidate.input(req.body, 'change' +
|
|
(req.authDetails.level === 'admin'? 'admin' : ''));
|
|
if (error) return res400(error, res);
|
|
|
|
if (user.hasOwnProperty('pass')) {
|
|
user.pass = bcrypt.hashSync(user.pass, 10);
|
|
}
|
|
|
|
// check that user does not already exist if new name was specified
|
|
if (user.hasOwnProperty('name') && user.name !== username) {
|
|
if (!await usernameCheck(user.name, res, next)) return;
|
|
}
|
|
|
|
// get current mail address to compare to given address
|
|
const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err));
|
|
|
|
await UserModel.findOneAndUpdate({name: username}, user, {new: true}).log(req).lean().exec( (err, data:any) => {
|
|
if (err) return next(err);
|
|
if (data) {
|
|
if (data.mail !== oldUserData.email) { // mail address was changed, send notice to old address
|
|
Mail.send(oldUserData.email, 'Email change in your DeFinMa database account',
|
|
'Hi, <br><br> Your email address of your DeFinMa account was changed to ' + data.mail +
|
|
'<br><br>If you actually did this, just delete this email.' +
|
|
'<br><br>If you did not change your email, someone might be messing around with your account, ' +
|
|
'so talk to the sysadmin quickly!<br><br>Have a nice day.' +
|
|
'<br><br>The DeFinMa team');
|
|
}
|
|
res.json(UserValidate.output(data));
|
|
}
|
|
else {
|
|
res.status(404).json({status: 'Not found'});
|
|
}
|
|
});
|
|
});
|
|
|
|
// TODO: only possible if no data is linked to user, otherwise change status, etc.
|
|
// this path matches /user, /user/ and /user/xxx, but not /user/key or user/new.
|
|
// See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
|
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {
|
|
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
|
|
|
const username = getUsername(req, res);
|
|
if (!username) return;
|
|
|
|
UserModel.findOneAndDelete({name: username}).log(req).lean().exec( (err, data:any) => {
|
|
if (err) return next(err);
|
|
if (data) {
|
|
res.json({status: 'OK'})
|
|
}
|
|
else {
|
|
res.status(404).json({status: 'Not found'});
|
|
}
|
|
});
|
|
});
|
|
|
|
router.get('/user/key', (req, res, next) => {
|
|
if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return;
|
|
|
|
UserModel.findOne({name: req.authDetails.username}).lean().exec( (err, data:any) => {
|
|
if (err) return next(err);
|
|
res.json({key: data.key});
|
|
});
|
|
});
|
|
|
|
router.post('/user/new', async (req, res, next) => {
|
|
if (!req.auth(res, ['admin'], 'basic')) return;
|
|
|
|
// validate input
|
|
const {error, value: user} = UserValidate.input(req.body, 'new');
|
|
if (error) return res400(error, res);
|
|
|
|
// check that user does not already exist
|
|
if (!await usernameCheck(user.name, res, next)) return;
|
|
|
|
user.key = mongoose.Types.ObjectId(); // use object id as unique API key
|
|
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
|
|
user.pass = hash;
|
|
new UserModel(user).save((err, data) => { // store user
|
|
if (err) return next(err);
|
|
db.log(req, 'users', {_id: data._id}, data.toObject());
|
|
res.json(UserValidate.output(data.toObject()));
|
|
});
|
|
});
|
|
});
|
|
|
|
router.post('/user/passreset', (req, res, next) => {
|
|
// check if user/email combo exists
|
|
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
|
|
if (err) return next(err);
|
|
if (data.length === 1) { // it exists
|
|
const newPass = Math.random().toString(36).substring(2); // generate temporary password
|
|
bcrypt.hash(newPass, 10, (err, hash) => { // password hashing
|
|
if (err) return next(err);
|
|
|
|
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}).log(req).exec(err => { // write new password
|
|
if (err) return next(err);
|
|
|
|
// send email
|
|
Mail.send(data[0].email, 'Your new password for the DeFinMa database',
|
|
'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '' +
|
|
'<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.' +
|
|
'<br><br>The DeFinMa team', err => {
|
|
if (err) return next(err);
|
|
res.json({status: 'OK'});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
res.status(404).json({status: 'Not found'});
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
module.exports = router;
|
|
|
|
function getUsername (req, res) { // returns username or false if action is not allowed
|
|
req.params.username = req.params[0]; // because of path regex
|
|
if (req.params.username !== undefined) { // different username than request user
|
|
if (!req.auth(res, ['admin'], 'basic')) return false;
|
|
return req.params.username;
|
|
}
|
|
else {
|
|
return req.authDetails.username;
|
|
}
|
|
}
|
|
|
|
async function usernameCheck (name, res, next) { // check if username is already taken
|
|
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
|
|
if (userData instanceof Error) return false;
|
|
if (userData || UserValidate.isSpecialName(name)) {
|
|
res.status(400).json({status: 'Username already taken'});
|
|
return false;
|
|
}
|
|
return true;
|
|
} |