Working on password recovery

This commit is contained in:
2021-07-28 23:02:04 +10:00
parent 72b3babfd8
commit 81da8ca422
9 changed files with 224 additions and 1 deletions
+5
View File
@@ -14,6 +14,11 @@ router.post('/login', require('./login'));
//refresh token
router.post('/token', require('./token'));
//password recover and reset
router.post('/recover', require('./password-recover'));
router.get('/reset', require('./password-redirect'));
router.patch('/reset', require('./password-reset'));
//middleware
router.use(tokenAuth);
+106
View File
@@ -0,0 +1,106 @@
//libraries
const nodemailer = require('nodemailer');
const { accounts, recovery } = require('../database/models');
//utilities
const uuid = require('../utilities/uuid');
const validateEmail = require('../utilities/validate-email');
//auth/recover
const route = async (req, res) => {
//validate details
const validateErr = await validateDetails(req.body);
if (validateErr) {
return res.status(401).end(validateErr);
}
//recovery token
const token = uuid(32);
//send the recovery email
const emailErr = await sendRecoveryEmail(req.body.email, token);
if (emailErr) {
return res.status(500).send(emailErr);
}
//save the token
recovery.upsert({
email: req.body.email,
token: token
});
//finally
res.status(200).send("Validation email sent!");
return null;
};
const validateDetails = async (body) => {
//basic formatting
if (!validateEmail(body.email)) {
return 'Invalid email';
}
//check for existing email
const emailRecord = await accounts.findOne({
where: {
email: body.email
}
});
if (!emailRecord) {
return 'Invalid email';
}
//OK
return null;
};
const sendRecoveryEmail = async (email, token) => {
const addr = `${process.env.WEB_PROTOCOL}://${process.env.WEB_ADDRESS}/auth/validation?token=${token}`;
const msg = `Hello,
Please visit the following link to reset your password: ${addr}
If you did not request a password reset, you can safely ignore this message.
`;
let transporter, info;
//what exactly is a transport?
try {
transporter = nodemailer.createTransport({
host: process.env.MAIL_SMTP,
port: 465,
secure: true,
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD
},
});
}
catch(e) {
return `failed to create a mail transport: ${e}`;
}
// send mail with defined transport object
try {
info = await transporter.sendMail({
from: `recovery@${process.env.WEB_ADDRESS}`, //WARNING: google overwrites this
to: email,
subject: 'Password Recovery',
text: msg
});
}
catch(e) {
return `failed to send validation mail: ${e}`;
}
if (info.accepted[0] != email) {
return 'recovery email failed to send';
}
return null;
};
module.exports = route;
+19
View File
@@ -0,0 +1,19 @@
const { accounts, recovery } = require('../database/models');
//auth/reset
const route = async (req, res) => {
//verify the recovery record exists
const record = recovery.findOne({
token: req.query.token
});
if (!record) {
return res.status(401).end('Failed to recover a password');
}
//redirect to the front-end
res.redirect(`${process.env.WEB_PROTOCOL}${process.env.WEB_RESET_ADDRESS}?email=${record.email}&token=${record.token}`);
return null;
};
module.exports = route;
+59
View File
@@ -0,0 +1,59 @@
//libraries
const bcrypt = require('bcryptjs');
const { accounts, recovery } = require('../database/models');
//auth/reset
const route = async (req, res) => {
//validate the given details
const validateErr = await validateDetails(req.query, req.body);
if (validateErr) {
return res.status(401).send(validateErr);
}
//generate the password hash
const hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
//update the account data
accounts.update({
hash: hash
}, {
where: {
email: req.query.email
}
})
//delete from the recovery table
recovery.destroy({
where: {
email: req.query.email
}
});
return null;
};
const validateDetails = async (query, body) => {
//verify the recovery record exists
const record = recovery.findOne({
email: query.email,
token: query.token
});
if (!record) {
return 'Failed to recover a password';
}
//validate password
if (!body.password) {
return 'Missing password';
}
if (body.password.length < 8) {
return 'Password too short';
}
return null;
};
module.exports = route;