7c4f180540
There's duplication in the user and session endpoints that all return the same shaped user model data. The new helper should keep them consistent when new properties need to be exposed.
345 lines
9.1 KiB
JavaScript
345 lines
9.1 KiB
JavaScript
import crypto from 'crypto';
|
|
import async from 'async';
|
|
|
|
import User from '../models/user';
|
|
import mail from '../utils/mail';
|
|
import {
|
|
renderEmailConfirmation,
|
|
renderResetPassword,
|
|
} from '../views/mail';
|
|
|
|
export * from './user.controller/apiKey';
|
|
|
|
export function userResponse(user) {
|
|
return {
|
|
email: user.email,
|
|
username: user.username,
|
|
preferences: user.preferences,
|
|
apiKeys: user.apiKeys,
|
|
verified: user.verified,
|
|
id: user._id
|
|
};
|
|
}
|
|
|
|
const random = (done) => {
|
|
crypto.randomBytes(20, (err, buf) => {
|
|
const token = buf.toString('hex');
|
|
done(err, token);
|
|
});
|
|
};
|
|
|
|
export function findUserByUsername(username, cb) {
|
|
User.findOne(
|
|
{ username },
|
|
(err, user) => {
|
|
cb(user);
|
|
}
|
|
);
|
|
}
|
|
|
|
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
|
|
|
|
export function createUser(req, res, next) {
|
|
random((tokenError, token) => {
|
|
const user = new User({
|
|
username: req.body.username,
|
|
email: req.body.email,
|
|
password: req.body.password,
|
|
verified: User.EmailConfirmation.Sent,
|
|
verifiedToken: token,
|
|
verifiedTokenExpires: EMAIL_VERIFY_TOKEN_EXPIRY_TIME,
|
|
});
|
|
|
|
User.findOne(
|
|
{
|
|
$or: [
|
|
{ email: req.body.email },
|
|
{ username: req.body.username }
|
|
]
|
|
},
|
|
(err, existingUser) => {
|
|
if (err) {
|
|
res.status(404).send({ error: err });
|
|
return;
|
|
}
|
|
|
|
if (existingUser) {
|
|
const fieldInUse = existingUser.email === req.body.email ? 'Email' : 'Username';
|
|
res.status(422).send({ error: `${fieldInUse} is in use` });
|
|
return;
|
|
}
|
|
user.save((saveErr) => {
|
|
if (saveErr) {
|
|
next(saveErr);
|
|
return;
|
|
}
|
|
req.logIn(user, (loginErr) => {
|
|
if (loginErr) {
|
|
next(loginErr);
|
|
return;
|
|
}
|
|
|
|
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));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
export function duplicateUserCheck(req, res) {
|
|
const checkType = req.query.check_type;
|
|
const value = req.query[checkType];
|
|
const query = {};
|
|
query[checkType] = value;
|
|
User.findOne(query, (err, user) => {
|
|
if (user) {
|
|
return res.json({
|
|
exists: true,
|
|
message: `This ${checkType} is already taken.`,
|
|
type: checkType
|
|
});
|
|
}
|
|
return res.json({
|
|
exists: false,
|
|
type: checkType
|
|
});
|
|
});
|
|
}
|
|
|
|
export function updatePreferences(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;
|
|
}
|
|
|
|
const preferences = Object.assign({}, user.preferences, req.body.preferences);
|
|
user.preferences = preferences;
|
|
|
|
user.save((saveErr) => {
|
|
if (saveErr) {
|
|
res.status(500).json({ error: saveErr });
|
|
return;
|
|
}
|
|
|
|
res.json(user.preferences);
|
|
});
|
|
});
|
|
}
|
|
|
|
export function resetPasswordInitiate(req, res) {
|
|
async.waterfall([
|
|
random,
|
|
(token, done) => {
|
|
User.findOne({ email: 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
|
|
|
|
user.save((saveErr) => {
|
|
done(saveErr, token, user);
|
|
});
|
|
});
|
|
},
|
|
(token, user, done) => {
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
|
const mailOptions = renderResetPassword({
|
|
body: {
|
|
domain: `${protocol}://${req.headers.host}`,
|
|
link: `${protocol}://${req.headers.host}/reset-password/${token}`,
|
|
},
|
|
to: user.email,
|
|
});
|
|
|
|
mail.send(mailOptions, done);
|
|
}
|
|
], (err) => {
|
|
if (err) {
|
|
console.log(err);
|
|
res.json({ success: false });
|
|
return;
|
|
}
|
|
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
|
});
|
|
}
|
|
|
|
export function validateResetPasswordToken(req, res) {
|
|
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
|
if (!user) {
|
|
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
|
return;
|
|
}
|
|
res.json({ success: true });
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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: 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 {
|
|
user.verified = User.EmailConfirmation.Resent;
|
|
user.verifiedToken = token;
|
|
user.verifiedTokenExpires = EMAIL_VERIFY_TOKEN_EXPIRY_TIME; // 24 hours
|
|
user.save();
|
|
|
|
res.json(userResponse(req.user));
|
|
}
|
|
});
|
|
});
|
|
},
|
|
]);
|
|
}
|
|
|
|
export function verifyEmail(req, res) {
|
|
const token = req.query.t;
|
|
|
|
User.findOne({ verifiedToken: token, verifiedTokenExpires: { $gt: Date.now() } }, (err, user) => {
|
|
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 });
|
|
});
|
|
});
|
|
}
|
|
|
|
export function updatePassword(req, res) {
|
|
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
|
if (!user) {
|
|
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
|
return;
|
|
}
|
|
|
|
user.password = req.body.password;
|
|
user.resetPasswordToken = undefined;
|
|
user.resetPasswordExpires = undefined;
|
|
|
|
user.save((saveErr) => {
|
|
req.logIn(user, loginErr => res.json(userResponse(req.user)));
|
|
});
|
|
});
|
|
|
|
// eventually send email that the password has been reset
|
|
}
|
|
|
|
export function userExists(username, callback) {
|
|
User.findOne({ username }, (err, user) => (
|
|
user ? callback(true) : callback(false)
|
|
));
|
|
}
|
|
|
|
export function saveUser(res, user) {
|
|
user.save((saveErr) => {
|
|
if (saveErr) {
|
|
res.status(500).json({ error: saveErr });
|
|
return;
|
|
}
|
|
|
|
res.json(userResponse(user));
|
|
});
|
|
}
|
|
|
|
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) {
|
|
user.comparePassword(req.body.currentPassword, (passwordErr, isMatch) => {
|
|
if (passwordErr) throw passwordErr;
|
|
if (!isMatch) {
|
|
res.status(401).json({ error: 'Current password is invalid.' });
|
|
return;
|
|
}
|
|
user.password = req.body.newPassword;
|
|
saveUser(res, user);
|
|
});
|
|
} else if (user.email !== req.body.email) {
|
|
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);
|
|
|
|
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: user.email,
|
|
});
|
|
|
|
mail.send(mailOptions);
|
|
});
|
|
} else {
|
|
saveUser(res, user);
|
|
}
|
|
});
|
|
}
|
|
|