2016-10-12 21:19:43 +00:00
|
|
|
import crypto from 'crypto';
|
|
|
|
import async from 'async';
|
2017-06-26 16:48:28 +00:00
|
|
|
|
2017-02-22 19:29:35 +00:00
|
|
|
import User from '../models/user';
|
2017-06-26 16:48:28 +00:00
|
|
|
import mail from '../utils/mail';
|
|
|
|
import {
|
|
|
|
renderEmailConfirmation,
|
|
|
|
renderResetPassword,
|
|
|
|
} from '../views/mail';
|
2016-06-09 00:52:59 +00:00
|
|
|
|
2019-05-13 19:29:00 +00:00
|
|
|
export * from './user.controller/apiKey';
|
|
|
|
|
2019-05-22 15:57:09 +00:00
|
|
|
export function userResponse(user) {
|
|
|
|
return {
|
|
|
|
email: user.email,
|
|
|
|
username: user.username,
|
|
|
|
preferences: user.preferences,
|
|
|
|
apiKeys: user.apiKeys,
|
|
|
|
verified: user.verified,
|
2019-08-08 21:34:49 +00:00
|
|
|
id: user._id,
|
2019-09-25 21:54:46 +00:00
|
|
|
totalSize: user.totalSize
|
2019-05-22 15:57:09 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:48:28 +00:00
|
|
|
const random = (done) => {
|
|
|
|
crypto.randomBytes(20, (err, buf) => {
|
|
|
|
const token = buf.toString('hex');
|
|
|
|
done(err, token);
|
2016-06-09 00:52:59 +00:00
|
|
|
});
|
2017-06-26 16:48:28 +00:00
|
|
|
};
|
2016-06-09 00:52:59 +00:00
|
|
|
|
2017-07-11 15:37:43 +00:00
|
|
|
export function findUserByUsername(username, cb) {
|
2020-07-15 21:33:11 +00:00
|
|
|
User.findByUsername(username, (err, user) => {
|
2020-07-14 22:16:17 +00:00
|
|
|
cb(user);
|
|
|
|
});
|
2017-07-11 15:37:43 +00:00
|
|
|
}
|
|
|
|
|
2017-06-26 16:48:28 +00:00
|
|
|
export function createUser(req, res, next) {
|
2020-04-06 19:55:00 +00:00
|
|
|
const { username, email } = req.body;
|
2020-03-08 20:09:01 +00:00
|
|
|
const { password } = req.body;
|
2020-04-06 19:55:00 +00:00
|
|
|
const emailLowerCase = email.toLowerCase();
|
2019-09-19 17:38:27 +00:00
|
|
|
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
2017-06-26 16:48:28 +00:00
|
|
|
random((tokenError, token) => {
|
|
|
|
const user = new User({
|
2020-04-06 23:02:55 +00:00
|
|
|
username,
|
2020-04-06 19:55:00 +00:00
|
|
|
email: emailLowerCase,
|
2020-03-08 20:09:01 +00:00
|
|
|
password,
|
2017-06-26 16:48:28 +00:00
|
|
|
verified: User.EmailConfirmation.Sent,
|
|
|
|
verifiedToken: token,
|
|
|
|
verifiedTokenExpires: EMAIL_VERIFY_TOKEN_EXPIRY_TIME,
|
|
|
|
});
|
|
|
|
|
2020-07-15 21:33:11 +00:00
|
|
|
User.findByEmailAndUsername(email, username, (err, existingUser) => {
|
2020-07-14 22:16:17 +00:00
|
|
|
if (err) {
|
|
|
|
res.status(404).send({ error: err });
|
|
|
|
return;
|
|
|
|
}
|
2016-06-09 00:52:59 +00:00
|
|
|
|
2020-07-14 22:16:17 +00:00
|
|
|
if (existingUser) {
|
|
|
|
const fieldInUse = existingUser.email.toLowerCase() === emailLowerCase ? 'Email' : 'Username';
|
|
|
|
res.status(422).send({ error: `${fieldInUse} is in use` });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
user.save((saveErr) => {
|
|
|
|
if (saveErr) {
|
|
|
|
next(saveErr);
|
2017-02-22 19:29:35 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-07-14 22:16:17 +00:00
|
|
|
req.logIn(user, (loginErr) => {
|
|
|
|
if (loginErr) {
|
|
|
|
next(loginErr);
|
2017-02-22 19:29:35 +00:00
|
|
|
return;
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
2020-07-14 22:16:17 +00:00
|
|
|
|
|
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
|
|
|
const mailOptions = renderEmailConfirmation({
|
|
|
|
body: {
|
|
|
|
domain: `${protocol}://${req.headers.host}`,
|
|
|
|
link: `${protocol}://${req.headers.host}/verify?t=${token}`
|
|
|
|
},
|
|
|
|
to: req.user.email,
|
|
|
|
});
|
|
|
|
|
|
|
|
mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars
|
|
|
|
res.json(userResponse(req.user));
|
2016-06-23 22:29:55 +00:00
|
|
|
});
|
|
|
|
});
|
2020-07-14 22:16:17 +00:00
|
|
|
});
|
|
|
|
});
|
2017-06-26 16:48:28 +00:00
|
|
|
});
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
2016-08-05 01:43:13 +00:00
|
|
|
|
2016-09-02 18:51:30 +00:00
|
|
|
export function duplicateUserCheck(req, res) {
|
|
|
|
const checkType = req.query.check_type;
|
|
|
|
const value = req.query[checkType];
|
|
|
|
const query = {};
|
2020-07-14 22:16:17 +00:00
|
|
|
query[checkType] = value;
|
2020-07-15 21:33:11 +00:00
|
|
|
// Don't want to use findByEmailOrUsername here, because in this case we do
|
|
|
|
// want to use case-insensitive search for usernames to prevent username
|
|
|
|
// duplicates, which overrides the default behavior.
|
2020-07-14 22:16:17 +00:00
|
|
|
User.findOne(query).collation({ locale: 'en', strength: 2 }).exec((err, user) => {
|
2016-09-02 18:51:30 +00:00
|
|
|
if (user) {
|
|
|
|
return res.json({
|
|
|
|
exists: true,
|
|
|
|
message: `This ${checkType} is already taken.`,
|
|
|
|
type: checkType
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return res.json({
|
|
|
|
exists: false,
|
|
|
|
type: checkType
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-08-05 01:43:13 +00:00
|
|
|
export function updatePreferences(req, res) {
|
2016-08-09 18:20:54 +00:00
|
|
|
User.findById(req.user.id, (err, user) => {
|
|
|
|
if (err) {
|
2017-02-22 19:29:35 +00:00
|
|
|
res.status(500).json({ error: err });
|
|
|
|
return;
|
2016-08-09 18:20:54 +00:00
|
|
|
}
|
2016-11-17 16:15:35 +00:00
|
|
|
if (!user) {
|
2017-02-22 19:29:35 +00:00
|
|
|
res.status(404).json({ error: 'Document not found' });
|
|
|
|
return;
|
2016-08-09 18:20:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const preferences = Object.assign({}, user.preferences, req.body.preferences);
|
|
|
|
user.preferences = preferences;
|
|
|
|
|
2017-02-22 19:29:35 +00:00
|
|
|
user.save((saveErr) => {
|
|
|
|
if (saveErr) {
|
|
|
|
res.status(500).json({ error: saveErr });
|
|
|
|
return;
|
2016-08-09 18:20:54 +00:00
|
|
|
}
|
|
|
|
|
2017-02-22 19:29:35 +00:00
|
|
|
res.json(user.preferences);
|
2016-08-09 18:20:54 +00:00
|
|
|
});
|
2016-11-17 16:15:35 +00:00
|
|
|
});
|
2016-08-05 01:43:13 +00:00
|
|
|
}
|
2016-10-12 18:25:24 +00:00
|
|
|
|
|
|
|
export function resetPasswordInitiate(req, res) {
|
2016-10-12 21:19:43 +00:00
|
|
|
async.waterfall([
|
2017-06-26 16:48:28 +00:00
|
|
|
random,
|
2016-10-12 21:19:43 +00:00
|
|
|
(token, done) => {
|
2020-07-15 21:33:11 +00:00
|
|
|
User.findByEmail(req.body.email, (err, user) => {
|
|
|
|
if (!user) {
|
|
|
|
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
user.resetPasswordToken = token;
|
|
|
|
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
|
2016-10-12 21:19:43 +00:00
|
|
|
|
2020-07-15 21:33:11 +00:00
|
|
|
user.save((saveErr) => {
|
|
|
|
done(saveErr, token, user);
|
2016-10-12 21:19:43 +00:00
|
|
|
});
|
2020-07-15 21:33:11 +00:00
|
|
|
});
|
2016-10-18 02:56:19 +00:00
|
|
|
},
|
|
|
|
(token, user, done) => {
|
2017-06-26 17:48:24 +00:00
|
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
2017-06-26 16:48:28 +00:00
|
|
|
const mailOptions = renderResetPassword({
|
|
|
|
body: {
|
2017-06-26 17:48:24 +00:00
|
|
|
domain: `${protocol}://${req.headers.host}`,
|
|
|
|
link: `${protocol}://${req.headers.host}/reset-password/${token}`,
|
2017-06-26 16:48:28 +00:00
|
|
|
},
|
2016-10-18 02:56:19 +00:00
|
|
|
to: user.email,
|
2017-01-13 15:35:39 +00:00
|
|
|
});
|
2017-06-26 16:48:28 +00:00
|
|
|
|
|
|
|
mail.send(mailOptions, done);
|
2016-10-12 21:19:43 +00:00
|
|
|
}
|
|
|
|
], (err) => {
|
|
|
|
if (err) {
|
|
|
|
console.log(err);
|
2017-02-22 19:29:35 +00:00
|
|
|
res.json({ success: false });
|
|
|
|
return;
|
2016-10-12 18:25:24 +00:00
|
|
|
}
|
2017-02-22 19:29:35 +00:00
|
|
|
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
2016-10-12 18:25:24 +00:00
|
|
|
});
|
|
|
|
}
|
2016-10-18 20:07:25 +00:00
|
|
|
|
|
|
|
export function validateResetPasswordToken(req, res) {
|
|
|
|
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
|
|
|
if (!user) {
|
2017-02-22 19:29:35 +00:00
|
|
|
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
|
|
|
return;
|
2016-10-18 20:07:25 +00:00
|
|
|
}
|
|
|
|
res.json({ success: true });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:48:28 +00:00
|
|
|
export function emailVerificationInitiate(req, res) {
|
|
|
|
async.waterfall([
|
|
|
|
random,
|
|
|
|
(token, done) => {
|
|
|
|
User.findById(req.user.id, (err, user) => {
|
|
|
|
if (err) {
|
|
|
|
res.status(500).json({ error: err });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!user) {
|
|
|
|
res.status(404).json({ error: 'Document not found' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (user.verified === User.EmailConfirmation.Verified) {
|
|
|
|
res.status(409).json({ error: 'Email already verified' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-26 17:48:24 +00:00
|
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
2017-06-26 16:48:28 +00:00
|
|
|
const mailOptions = renderEmailConfirmation({
|
|
|
|
body: {
|
2017-06-26 17:48:24 +00:00
|
|
|
domain: `${protocol}://${req.headers.host}`,
|
|
|
|
link: `${protocol}://${req.headers.host}/verify?t=${token}`
|
2017-06-26 16:48:28 +00:00
|
|
|
},
|
|
|
|
to: user.email,
|
|
|
|
});
|
|
|
|
|
|
|
|
mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars
|
|
|
|
if (mailErr != null) {
|
|
|
|
res.status(500).send({ error: 'Error sending mail' });
|
|
|
|
} else {
|
2019-09-19 17:38:27 +00:00
|
|
|
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
2017-06-26 16:48:28 +00:00
|
|
|
user.verified = User.EmailConfirmation.Resent;
|
|
|
|
user.verifiedToken = token;
|
|
|
|
user.verifiedTokenExpires = EMAIL_VERIFY_TOKEN_EXPIRY_TIME; // 24 hours
|
|
|
|
user.save();
|
|
|
|
|
2019-05-22 15:57:09 +00:00
|
|
|
res.json(userResponse(req.user));
|
2017-06-26 16:48:28 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function verifyEmail(req, res) {
|
|
|
|
const token = req.query.t;
|
|
|
|
|
2019-09-19 17:38:27 +00:00
|
|
|
User.findOne({ verifiedToken: token, verifiedTokenExpires: { $gt: new Date() } }, (err, user) => {
|
2017-06-26 16:48:28 +00:00
|
|
|
if (!user) {
|
|
|
|
res.status(401).json({ success: false, message: 'Token is invalid or has expired.' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
user.verified = User.EmailConfirmation.Verified;
|
|
|
|
user.verifiedToken = null;
|
|
|
|
user.verifiedTokenExpires = null;
|
|
|
|
user.save()
|
|
|
|
.then((result) => { // eslint-disable-line
|
|
|
|
res.json({ success: true });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-10-18 20:07:25 +00:00
|
|
|
export function updatePassword(req, res) {
|
2017-02-22 19:29:35 +00:00
|
|
|
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
2016-10-18 20:07:25 +00:00
|
|
|
if (!user) {
|
2017-02-22 19:29:35 +00:00
|
|
|
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
|
|
|
return;
|
2016-10-18 20:07:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
user.password = req.body.password;
|
|
|
|
user.resetPasswordToken = undefined;
|
|
|
|
user.resetPasswordExpires = undefined;
|
|
|
|
|
2017-02-22 19:29:35 +00:00
|
|
|
user.save((saveErr) => {
|
2019-05-22 15:57:09 +00:00
|
|
|
req.logIn(user, loginErr => res.json(userResponse(req.user)));
|
2016-10-18 20:07:25 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-11-17 16:15:35 +00:00
|
|
|
// eventually send email that the password has been reset
|
2016-10-18 20:07:25 +00:00
|
|
|
}
|
2017-01-06 18:08:03 +00:00
|
|
|
|
|
|
|
export function userExists(username, callback) {
|
2020-07-15 21:33:11 +00:00
|
|
|
User.findByUsername(username, (err, user) => (
|
2017-01-06 18:08:03 +00:00
|
|
|
user ? callback(true) : callback(false)
|
|
|
|
));
|
|
|
|
}
|
2017-03-16 22:25:12 +00:00
|
|
|
|
2017-04-06 18:34:14 +00:00
|
|
|
export function saveUser(res, user) {
|
|
|
|
user.save((saveErr) => {
|
|
|
|
if (saveErr) {
|
|
|
|
res.status(500).json({ error: saveErr });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-22 15:57:09 +00:00
|
|
|
res.json(userResponse(user));
|
2017-04-06 18:34:14 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-16 22:25:12 +00:00
|
|
|
export function updateSettings(req, res) {
|
|
|
|
User.findById(req.user.id, (err, user) => {
|
|
|
|
if (err) {
|
|
|
|
res.status(500).json({ error: err });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!user) {
|
|
|
|
res.status(404).json({ error: 'Document not found' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
user.username = req.body.username;
|
|
|
|
|
|
|
|
if (req.body.currentPassword) {
|
2017-04-06 18:34:14 +00:00
|
|
|
user.comparePassword(req.body.currentPassword, (passwordErr, isMatch) => {
|
|
|
|
if (passwordErr) throw passwordErr;
|
2017-03-16 22:25:12 +00:00
|
|
|
if (!isMatch) {
|
|
|
|
res.status(401).json({ error: 'Current password is invalid.' });
|
|
|
|
return;
|
|
|
|
}
|
2017-04-06 18:34:14 +00:00
|
|
|
user.password = req.body.newPassword;
|
|
|
|
saveUser(res, user);
|
2017-03-16 22:25:12 +00:00
|
|
|
});
|
2017-06-26 16:48:28 +00:00
|
|
|
} else if (user.email !== req.body.email) {
|
2019-09-19 17:38:27 +00:00
|
|
|
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
2017-06-26 16:48:28 +00:00
|
|
|
user.verified = User.EmailConfirmation.Sent;
|
|
|
|
|
|
|
|
user.email = req.body.email;
|
|
|
|
|
|
|
|
random((error, token) => {
|
|
|
|
user.verifiedToken = token;
|
|
|
|
user.verifiedTokenExpires = EMAIL_VERIFY_TOKEN_EXPIRY_TIME;
|
|
|
|
|
|
|
|
saveUser(res, user);
|
|
|
|
|
2017-06-26 17:48:24 +00:00
|
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
2017-06-26 16:48:28 +00:00
|
|
|
const mailOptions = renderEmailConfirmation({
|
|
|
|
body: {
|
2017-06-26 17:48:24 +00:00
|
|
|
domain: `${protocol}://${req.headers.host}`,
|
|
|
|
link: `${protocol}://${req.headers.host}/verify?t=${token}`
|
2017-06-26 16:48:28 +00:00
|
|
|
},
|
|
|
|
to: user.email,
|
|
|
|
});
|
|
|
|
|
|
|
|
mail.send(mailOptions);
|
|
|
|
});
|
2017-03-16 22:25:12 +00:00
|
|
|
} else {
|
|
|
|
saveUser(res, user);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-10-14 19:08:36 +00:00
|
|
|
|