revises and simplifies forgot password routes
This commit is contained in:
@@ -4,159 +4,153 @@ const nodemailer = require('nodemailer');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const passwordResetRoutes = (emailConfig, User) => {
|
||||
const mockEmailHandler = async () => {
|
||||
const testAccount = await nodemailer.createTestAccount();
|
||||
|
||||
const smtpOptions = {
|
||||
...emailConfig,
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false,
|
||||
fromName: 'John Doe',
|
||||
fromAddress: 'john.doe@payloadcms.com',
|
||||
auth: {
|
||||
user: testAccount.user,
|
||||
pass: testAccount.pass,
|
||||
},
|
||||
};
|
||||
|
||||
return nodemailer.createTransport(smtpOptions);
|
||||
};
|
||||
|
||||
|
||||
const sendResetEmail = async (req, res, next) => {
|
||||
let emailHandler;
|
||||
|
||||
try {
|
||||
switch (emailConfig.provider) {
|
||||
case 'mock':
|
||||
emailHandler = await mockEmailHandler();
|
||||
break;
|
||||
case 'smtp':
|
||||
emailHandler = nodemailer.createTransport(emailConfig);
|
||||
break;
|
||||
default:
|
||||
emailHandler = await mockEmailHandler(req, res, next, emailConfig);
|
||||
}
|
||||
|
||||
const generateToken = () => new Promise(resolve => crypto.randomBytes(20, (err, buffer) => resolve(buffer.toString('hex'))));
|
||||
|
||||
const token = await generateToken();
|
||||
|
||||
User.findOne({ email: req.body.userEmail }, (err, user) => {
|
||||
if (!user) {
|
||||
const message = `No account with email ${req.body.userEmail} address exists.`;
|
||||
return res.status(400).json({ message });
|
||||
}
|
||||
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordExpiration = Date.now() + 3600000; // 1 hour
|
||||
|
||||
user.save(async (saveError) => {
|
||||
if (saveError) {
|
||||
const message = 'Error saving temporary reset token';
|
||||
console.error(message, saveError);
|
||||
res.status(500).json({ message });
|
||||
}
|
||||
|
||||
console.log('Temporary reset token created and saved to DB');
|
||||
|
||||
const emailText = `You are receiving this because you (or someone else) have requested the reset of the password for your account.
|
||||
Please click on the following link, or paste this into your browser to complete the process:
|
||||
${req.protocol}://${req.headers.host}/reset/${token}
|
||||
If you did not request this, please ignore this email and your password will remain unchanged.`;
|
||||
|
||||
await emailHandler.sendMail({
|
||||
from: `"${emailConfig.fromName}" <${emailConfig.fromAddress}>`,
|
||||
to: req.body.userEmail,
|
||||
subject: req.body.subject || 'Password Reset',
|
||||
text: emailText,
|
||||
});
|
||||
res.sendStatus(200);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ success: false, error: 'Unable to send e-mail' });
|
||||
}
|
||||
};
|
||||
|
||||
const validateForgotRequestBody = (req, res, next) => {
|
||||
if (Object.prototype.hasOwnProperty.call(req.body, 'userEmail')) {
|
||||
next();
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
message: 'Missing userEmail in request body',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const validateResetPasswordBody = (req, res, next) => {
|
||||
if (Object.prototype.hasOwnProperty.call(req.body, 'token') && Object.prototype.hasOwnProperty.call(req.body, 'password')) {
|
||||
next();
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
message: 'Invalid request body',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = (req, res, User) => {
|
||||
User.findOne(
|
||||
{
|
||||
resetPasswordToken: req.body.token,
|
||||
resetPasswordExpiration: { $gt: Date.now() },
|
||||
},
|
||||
async (err, user) => {
|
||||
if (!user) {
|
||||
const message = 'Password reset token is invalid or has expired.';
|
||||
console.error(message);
|
||||
return res.status(400).json({ message });
|
||||
}
|
||||
|
||||
const errorMessage = 'Error setting new user password';
|
||||
try {
|
||||
await user.setPassword(req.body.password);
|
||||
user.resetPasswordExpiration = Date.now();
|
||||
await user.save((saveError) => {
|
||||
if (saveError) {
|
||||
console.error(errorMessage);
|
||||
return res.status(500).json({ message: errorMessage });
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'Password successfully reset' });
|
||||
});
|
||||
} catch (e) {
|
||||
return res.status(500).json({ message: errorMessage });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
router
|
||||
.route('/forgot')
|
||||
.post(
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
(req, res, next) => validateForgotRequestBody(req, res, next),
|
||||
(req, res, next) => sendResetEmail(req, res, next, emailConfig, User)
|
||||
validateForgotRequestBody,
|
||||
sendResetEmail,
|
||||
);
|
||||
|
||||
router
|
||||
.route('/reset')
|
||||
.post(
|
||||
(req, res, next) => validateResetPasswordBody(req, res, next),
|
||||
(req, res) => resetPassword(req, res, User)
|
||||
(req, res) => resetPassword(req, res, User),
|
||||
);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
const sendResetEmail = async (req, res, next, emailConfig, User) => {
|
||||
let emailHandler;
|
||||
|
||||
try {
|
||||
switch (emailConfig.provider) {
|
||||
case 'mock':
|
||||
emailHandler = await mockEmailHandler(req, res, next, emailConfig);
|
||||
break;
|
||||
case 'smtp':
|
||||
emailHandler = smtpEmailHandler(req, res, next, emailConfig);
|
||||
break;
|
||||
default:
|
||||
emailHandler = await mockEmailHandler(req, res, next, emailConfig);
|
||||
}
|
||||
|
||||
const generateToken = () => new Promise(resolve =>
|
||||
crypto.randomBytes(20, (err, buffer) =>
|
||||
resolve(buffer.toString('hex'))
|
||||
));
|
||||
|
||||
let token = await generateToken();
|
||||
|
||||
User.findOne({ email: req.body.userEmail }, (err, user) => {
|
||||
if (!user) {
|
||||
const message = `No account with email ${req.body.userEmail} address exists.`;
|
||||
return res.status(400).json({ message: message })
|
||||
}
|
||||
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordExpiration = Date.now() + 3600000; // 1 hour
|
||||
user.save(async (saveError) => {
|
||||
if (saveError) {
|
||||
const message = 'Error saving temporary reset token';
|
||||
console.error(message, saveError);
|
||||
res.status(500).json({ message });
|
||||
}
|
||||
console.log('Temporary reset token created and saved to DB');
|
||||
|
||||
|
||||
const emailText = `You are receiving this because you (or someone else) have requested the reset of the password for your account.
|
||||
Please click on the following link, or paste this into your browser to complete the process:
|
||||
${req.protocol}://${req.headers.host}/reset/${token}
|
||||
If you did not request this, please ignore this email and your password will remain unchanged.`;
|
||||
|
||||
await emailHandler.sendMail({
|
||||
from: `"${emailConfig.fromName}" <${emailConfig.fromAddress}>`,
|
||||
to: req.body.userEmail,
|
||||
subject: req.body.subject || 'Password Reset',
|
||||
text: emailText,
|
||||
});
|
||||
res.sendStatus(200);
|
||||
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ success: false, error: 'Unable to send e-mail' });
|
||||
}
|
||||
};
|
||||
|
||||
const validateForgotRequestBody = (req, res, next) => {
|
||||
if (req.body.hasOwnProperty('userEmail')) {
|
||||
next();
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
message: 'Missing userEmail in request body'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const validateResetPasswordBody = (req, res, next) => {
|
||||
if (req.body.hasOwnProperty('token') && req.body.hasOwnProperty('password')) {
|
||||
next();
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
message: 'Invalid request body'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = (req, res, User) => {
|
||||
User.findOne(
|
||||
{
|
||||
resetPasswordToken: req.body.token,
|
||||
resetPasswordExpiration: { $gt: Date.now() }
|
||||
},
|
||||
async (err, user) => {
|
||||
if (!user) {
|
||||
const message = 'Password reset token is invalid or has expired.';
|
||||
console.error(message);
|
||||
return res.status(400).json({ message });
|
||||
}
|
||||
|
||||
const errorMessage = 'Error setting new user password';
|
||||
try {
|
||||
await user.setPassword(req.body.password);
|
||||
user.resetPasswordExpiration = Date.now();
|
||||
await user.save(saveError => {
|
||||
if (saveError) {
|
||||
console.error(errorMessage);
|
||||
return res.status(500).json({ message: errorMessage });
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'Password successfully reset' });
|
||||
});
|
||||
} catch (e) {
|
||||
return res.status(500).json({ message: errorMessage });
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const mockEmailHandler = async (req, res, next, emailConfig) => {
|
||||
let testAccount = await nodemailer.createTestAccount();
|
||||
process.env.EMAIL_USER = testAccount.user;
|
||||
process.env.EMAIL_PASS = testAccount.pass;
|
||||
emailConfig.host = 'smtp.ethereal.email';
|
||||
emailConfig.port = 587;
|
||||
emailConfig.secure = false;
|
||||
emailConfig.fromName = 'John Doe';
|
||||
emailConfig.fromAddress = 'john.doe@payloadcms.com';
|
||||
return smtpEmailHandler(req, res, next, emailConfig);
|
||||
};
|
||||
|
||||
const smtpEmailHandler = (req, res, next, emailConfig) => {
|
||||
return nodemailer.createTransport({
|
||||
host: emailConfig.host,
|
||||
port: emailConfig.port,
|
||||
secure: emailConfig.secure, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = passwordResetRoutes;
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = (config, User) => ({
|
||||
* @returns {*}
|
||||
*/
|
||||
register: (req, res, next) => {
|
||||
const usernameField = config.user.useAsUsername || 'email';
|
||||
const usernameField = config.user.auth.useAsUsername;
|
||||
|
||||
User.register(new User({ usernameField: req.body[usernameField] }), req.body.password, (err, user) => {
|
||||
if (err) {
|
||||
@@ -36,7 +36,7 @@ module.exports = (config, User) => ({
|
||||
* @returns {*}
|
||||
*/
|
||||
login: (req, res) => {
|
||||
const usernameField = config.user.useAsUsername || 'email';
|
||||
const usernameField = config.user.auth.useAsUsername;
|
||||
const username = req.body[usernameField];
|
||||
const { password } = req.body;
|
||||
|
||||
|
||||
@@ -19,36 +19,32 @@ const authRoutes = (config, User) => {
|
||||
|
||||
router
|
||||
.route('/me')
|
||||
.post(passport.authenticate(config.user.auth.strategy, { session: false }), auth.me);
|
||||
.post(passport.authenticate('jwt', { session: false }), auth.me);
|
||||
|
||||
if (config.user.auth.passwordResets) {
|
||||
router.use('', passwordResetRoutes(config.user.email, User));
|
||||
}
|
||||
router.use('', passwordResetRoutes(config.email, User));
|
||||
|
||||
if (config.user.auth.registration) {
|
||||
router
|
||||
.route(`${config.user.slug}/register`) // TODO: not sure how to incorporate url params like `:pageId`
|
||||
.post(auth.register);
|
||||
router
|
||||
.route(`${config.user.slug}/register`)
|
||||
.post(auth.register);
|
||||
|
||||
router
|
||||
.route('/first-register')
|
||||
.post((req, res, next) => {
|
||||
User.countDocuments({}, (err, count) => {
|
||||
if (err) res.status(500).json({ error: err });
|
||||
if (count >= 1) return res.status(403).json({ initialized: true });
|
||||
return next();
|
||||
});
|
||||
}, (req, res, next) => {
|
||||
User.register(new User(req.body), req.body.password, (err) => {
|
||||
if (err) {
|
||||
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
|
||||
return res.status(httpStatus.UNAUTHORIZED).json(error);
|
||||
}
|
||||
router
|
||||
.route('/first-register')
|
||||
.post((req, res, next) => {
|
||||
User.countDocuments({}, (err, count) => {
|
||||
if (err) res.status(500).json({ error: err });
|
||||
if (count >= 1) return res.status(403).json({ initialized: true });
|
||||
return next();
|
||||
});
|
||||
}, (req, res, next) => {
|
||||
User.register(new User(req.body), req.body.password, (err) => {
|
||||
if (err) {
|
||||
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
|
||||
return res.status(httpStatus.UNAUTHORIZED).json(error);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}, auth.login);
|
||||
}
|
||||
return next();
|
||||
});
|
||||
}, auth.login);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -49,8 +49,8 @@ const CreateFirstUser = (props) => {
|
||||
|
||||
const fields = [...config.user.fields];
|
||||
|
||||
if (config.user.passwordIndex) {
|
||||
fields.splice(config.user.passwordIndex, 0, passwordField);
|
||||
if (config.user.auth.passwordIndex) {
|
||||
fields.splice(config.user.auth.passwordIndex, 0, passwordField);
|
||||
} else {
|
||||
fields.push(passwordField);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user