Slight refactor

This commit is contained in:
2019-05-20 00:34:47 +10:00
parent e1c21f509e
commit 9bae98e055
2 changed files with 298 additions and 318 deletions
+279 -293
View File
@@ -10,261 +10,251 @@ let sendmail = require('sendmail')();
let { validateEmail } = require('../common/utilities.js'); let { validateEmail } = require('../common/utilities.js');
let { throttle, isThrottled } = require('../common/throttle.js'); let { throttle, isThrottled } = require('../common/throttle.js');
function signup(connection) { const signup = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email) || fields.username.length < 4 || fields.username.length > 100 || fields.password.length < 8 || fields.password !== fields.retype) {
res.status(400).write('Invalid signup data');
res.end();
return;
}
//check if email, username already exists
let query = 'SELECT (SELECT COUNT(*) FROM accounts WHERE email = ?) AS email, (SELECT COUNT(*) FROM accounts WHERE username = ?) AS username;';
connection.query(query, [fields.email, fields.username], (err, results) => {
if (err) throw err; if (err) throw err;
//validate email, username and password if (results[0].email !== 0) {
if (!validateEmail(fields.email) || fields.username.length < 4 || fields.username.length > 100 || fields.password.length < 8 || fields.password !== fields.retype) { res.status(400).write('Email already registered!');
res.status(400).write('Invalid signup data');
res.end(); res.end();
return; return;
} }
//check if email, username already exists if (results[0].username !== 0) {
let query = 'SELECT (SELECT COUNT(*) FROM accounts WHERE email = ?) AS email, (SELECT COUNT(*) FROM accounts WHERE username = ?) AS username;'; res.status(400).write('Username already registered!');
connection.query(query, [fields.email, fields.username], (err, results) => { res.end();
return;
}
//generate the salt, hash
bcrypt.genSalt(11, (err, salt) => {
if (err) throw err; if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
if (results[0].email !== 0) {
res.status(400).write('Email already registered!');
res.end();
return;
}
if (results[0].username !== 0) {
res.status(400).write('Username already registered!');
res.end();
return;
}
//generate the salt, hash
bcrypt.genSalt(11, (err, salt) => {
if (err) throw err; if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
//generate a random number as a token
let rand = Math.floor(Math.random() * 100000);
//save the generated data to the signups table
let query = 'REPLACE INTO signups (email, username, salt, hash, verify) VALUES (?, ?, ?, ?, ?);';
connection.query(query, [fields.email, fields.username, salt, hash, rand], (err) => {
if (err) throw err; if (err) throw err;
//generate a random number as a token //prevent too many clicks
let rand = Math.floor(Math.random() * 100000); if (isThrottled(fields.email)) {
res.status(400).write('signup throttled');
res.end();
return;
}
//save the generated data to the signups table throttle(fields.email);
let query = 'REPLACE INTO signups (email, username, salt, hash, verify) VALUES (?, ?, ?, ?, ?);';
connection.query(query, [fields.email, fields.username, salt, hash, rand], (err) => {
if (err) throw err;
//prevent too many clicks //build the verification email
if (isThrottled(fields.email)) { let addr = `http://${process.env.WEB_ADDRESS}/verify?email=${fields.email}&verify=${rand}`;
res.status(400).write('signup throttled'); let msg = 'Hello! Please visit the following address to verify your account: ';
let msgHtml = `<html><body><p>${msg}<a href='${addr}'>${addr}</a></p></body></html>`;
//send the verification email
sendmail({
from: `signup@${process.env.WEB_ADDRESS}`,
to: fields.email,
subject: 'Email Verification',
text: msg + addr,
html: msgHtml
}, (err, reply) => {
//final check
if (err) {
res.write(`<p>Something went wrong (did you use a valid email?)</p><p>${err}</p>`);
res.end(); res.end();
return; return;
} }
throttle(fields.email); res.status(200).write('Verification email sent!');
res.end();
//build the verification email
let addr = `http://${process.env.WEB_ADDRESS}/verify?email=${fields.email}&verify=${rand}`;
let msg = 'Hello! Please visit the following address to verify your account: ';
let msgHtml = `<html><body><p>${msg}<a href='${addr}'>${addr}</a></p></body></html>`;
//send the verification email
sendmail({
from: `signup@${process.env.WEB_ADDRESS}`,
to: fields.email,
subject: 'Email Verification',
text: msg + addr,
html: msgHtml
}, (err, reply) => {
//final check
if (err) {
res.write(`<p>Something went wrong (did you use a valid email?)</p><p>${err}</p>`);
res.end();
return;
}
res.status(200).write('Verification email sent!');
res.end();
});
})
});
});
});
});
}
}
function verify(connection) {
return (req, res) => {
//get the saved data
let query = 'SELECT email, username, salt, hash, verify FROM signups WHERE email = ?;';
connection.query(query, [req.query.email], (err, results) => {
if (err) throw err;
//correct number of results
if (results.length != 1) {
res.write('<p>That account does not exist or this link has already been used.</p>');
res.end();
return;
}
//verify the link
if (req.query.verify != results[0].verify) {
res.write('<p>Verification failed!</p>');
res.end();
return;
}
//move the data from signups to accounts
let query = 'INSERT INTO accounts (email, username, salt, hash) VALUES (?, ?, ?, ?);';
connection.query(query, [results[0].email, results[0].username, results[0].salt, results[0].hash], (err) => {
if (err) throw err;
//delete from signups
let query = 'DELETE FROM signups WHERE email = ?;';
connection.query(query, [results[0].email], (err) => {
if (err) throw err;
res.write('<p>Verification succeeded!</p>');
res.end();
});
});
});
}
}
function login(connection) {
return (req, res) => {
//formidable handles forms
let form = formidable.IncomingForm();
//parse form
form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email) || fields.password.length < 8) {
res.write('<p>Invalid login data</p>');
res.end();
return;
}
//find this email's information
let query = 'SELECT id, username, salt, hash FROM accounts WHERE email = ?;';
connection.query(query, [fields.email], (err, results) => {
if (err) throw err;
//found this email?
if (results.length === 0) {
res.status(400).write('Incorrect email or password');
res.end();
return;
}
//gen a new hash from the salt and password
bcrypt.hash(fields.password, results[0].salt, (err, newHash) => {
if (err) throw err;
//compare the passwords
if (results[0].hash !== newHash) {
res.status(400).write('Incorrect email or password');
res.end();
return;
}
//create the new session
let rand = Math.floor(Math.random() * 100000);
let query = 'INSERT INTO sessions (accountId, token) VALUES (?, ?);';
connection.query(query, [results[0].id, rand], (err) => {
if (err) throw err;
//send json containing the account info
res.status(200).json({
id: results[0].id,
email: fields.email,
username: results[0].username,
token: rand
}); });
}); });
}); });
}); });
}); });
} });
} }
function logout(connection) { const verify = (connection) => (req, res) => {
return (req, res) => { //get the saved data
let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?) AND token = ?;'; let query = 'SELECT email, username, salt, hash, verify FROM signups WHERE email = ?;';
connection.query(query, [req.body.email, req.body.token], (err) => {
connection.query(query, [req.query.email], (err, results) => {
if (err) throw err;
//correct number of results
if (results.length != 1) {
res.write('<p>That account does not exist or this link has already been used.</p>');
res.end();
return;
}
//verify the link
if (req.query.verify != results[0].verify) {
res.write('<p>Verification failed!</p>');
res.end();
return;
}
//move the data from signups to accounts
let query = 'INSERT INTO accounts (email, username, salt, hash) VALUES (?, ?, ?, ?);';
connection.query(query, [results[0].email, results[0].username, results[0].salt, results[0].hash], (err) => {
if (err) throw err; if (err) throw err;
//delete from signups
let query = 'DELETE FROM signups WHERE email = ?;';
connection.query(query, [results[0].email], (err) => {
if (err) throw err;
res.write('<p>Verification succeeded!</p>');
res.end();
});
}); });
});
res.end();
}
} }
function passwordChange(connection) { const login = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email) || fields.password.length < 8) {
res.write('<p>Invalid login data</p>');
res.end();
return;
}
//find this email's information
let query = 'SELECT id, username, salt, hash FROM accounts WHERE email = ?;';
connection.query(query, [fields.email], (err, results) => {
if (err) throw err; if (err) throw err;
//validate password, retype //found this email?
if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) { if (results.length === 0) {
res.status(400).write('Invalid password change data'); res.status(400).write('Incorrect email or password');
res.end(); res.end();
return; return;
} }
//validate token //gen a new hash from the salt and password
query = 'SELECT sessions.token FROM sessions WHERE sessions.accountId IN (SELECT id FROM accounts WHERE email = ?);'; bcrypt.hash(fields.password, results[0].salt, (err, newHash) => {
connection.query(query, [fields.email], (err, results) => {
if (err) throw err; if (err) throw err;
let found = false; //compare the passwords
if (results[0].hash !== newHash) {
results.map((result) => { if (result.token == fields.token) found = true; }); res.status(400).write('Incorrect email or password');
if (!found) {
res.status(400).write('Invalid password change authentication');
res.end(); res.end();
return; return;
} }
//generate the new salt, hash //create the new session
bcrypt.genSalt(11, (err, salt) => { let rand = Math.floor(Math.random() * 100000);
let query = 'INSERT INTO sessions (accountId, token) VALUES (?, ?);';
connection.query(query, [results[0].id, rand], (err) => {
if (err) throw err; if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
//send json containing the account info
res.status(200).json({
id: results[0].id,
email: fields.email,
username: results[0].username,
token: rand
});
});
});
});
});
}
const logout = (connection) => (req, res) => {
let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?) AND token = ?;';
connection.query(query, [req.body.email, req.body.token], (err) => {
if (err) throw err;
});
res.end();
}
const passwordChange = (connection) => (req, res) => {
//formidable handles forms
let form = formidable.IncomingForm();
//parse form
form.parse(req, (err, fields) => {
if (err) throw err;
//validate password, retype
if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) {
res.status(400).write('Invalid password change data');
res.end();
return;
}
//validate token
query = 'SELECT sessions.token FROM sessions WHERE sessions.accountId IN (SELECT id FROM accounts WHERE email = ?);';
connection.query(query, [fields.email], (err, results) => {
if (err) throw err;
let found = false;
results.map((result) => { if (result.token == fields.token) found = true; });
if (!found) {
res.status(400).write('Invalid password change authentication');
res.end();
return;
}
//generate the new salt, hash
bcrypt.genSalt(11, (err, salt) => {
if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
if (err) throw err;
let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;';
connection.query(query, [salt, hash, fields.email], (err) => {
if (err) throw err; if (err) throw err;
let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;'; //clear all session data for this user (a 'feature')
connection.query(query, [salt, hash, fields.email], (err) => { let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?);';
connection.query(query, [fields.email], (err) => {
if (err) throw err; if (err) throw err;
//clear all session data for this user (a 'feature') //create the new session
let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?);'; let rand = Math.floor(Math.random() * 100000);
connection.query(query, [fields.email], (err) => {
let query = 'INSERT INTO sessions (accountId, token) VALUES ((SELECT accounts.id FROM accounts WHERE email = ?), ?);';
connection.query(query, [fields.email, rand], (err) => {
if (err) throw err; if (err) throw err;
//create the new session //send json containing the account info
let rand = Math.floor(Math.random() * 100000); res.status(200).json({
token: rand
let query = 'INSERT INTO sessions (accountId, token) VALUES ((SELECT accounts.id FROM accounts WHERE email = ?), ?);';
connection.query(query, [fields.email, rand], (err) => {
if (err) throw err;
//send json containing the account info
res.status(200).json({
token: rand
});
}); });
}); });
}); });
@@ -272,135 +262,131 @@ function passwordChange(connection) {
}); });
}); });
}); });
} });
} }
function passwordRecover(connection) { const passwordRecover = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email)) {
res.status(400).write('Invalid recover data');
res.end();
return;
}
//ensure that this email is registered to an account
let query = 'SELECT accounts.id FROM accounts WHERE email = ?;';
connection.query(query, [fields.email], (err, results) => {
if (err) throw err; if (err) throw err;
//validate email, username and password if (results.length !== 1) {
if (!validateEmail(fields.email)) { res.status(400).write('Invalid recover data (did you use a registered email?)');
res.status(400).write('Invalid recover data');
res.end(); res.end();
return; return;
} }
//ensure that this email is registered to an account //create the new recover record
let query = 'SELECT accounts.id FROM accounts WHERE email = ?;'; let rand = Math.floor(Math.random() * 100000);
connection.query(query, [fields.email], (err, results) => {
let query = 'REPLACE INTO passwordRecover (accountId, token) VALUES (?, ?)';
connection.query(query, [results[0].id, rand], (err) => {
if (err) throw err; if (err) throw err;
if (results.length !== 1) { //build the recovery email
res.status(400).write('Invalid recover data (did you use a registered email?)'); let addr = `http://${process.env.WEB_ADDRESS}/passwordreset?email=${fields.email}&token=${rand}`;
let msg = 'Hello! Please visit the following address to set a new password (if you didn\'t request a password recovery, ignore this email): ';
let msgHtml = `<html><body><p>${msg}<a href='${addr}'>${addr}</a></p></body></html>`;
//prevent too many clicks
if (isThrottled(fields.email)) {
res.status(400).write('recover throttled');
res.end(); res.end();
return; return;
} }
//create the new recover record throttle(fields.email);
let rand = Math.floor(Math.random() * 100000);
let query = 'REPLACE INTO passwordRecover (accountId, token) VALUES (?, ?)'; //send the verification email
connection.query(query, [results[0].id, rand], (err) => { sendmail({
if (err) throw err; from: `passwordrecover@${process.env.WEB_ADDRESS}`,
to: fields.email,
//build the recovery email subject: 'Password Recovery',
let addr = `http://${process.env.WEB_ADDRESS}/passwordreset?email=${fields.email}&token=${rand}`; text: msg + addr,
let msg = 'Hello! Please visit the following address to set a new password (if you didn\'t request a password recovery, ignore this email): '; html: msgHtml
let msgHtml = `<html><body><p>${msg}<a href='${addr}'>${addr}</a></p></body></html>`; }, (err, reply) => {
//final check
//prevent too many clicks if (err) {
if (isThrottled(fields.email)) { res.write(`<p>Something went wrong (did you use a valid email?)</p>${err}`)
res.status(400).write('recover throttled');
res.end(); res.end();
return; return;
} }
throttle(fields.email); res.status(200).write('Recovery email sent!');
res.end();
//send the verification email
sendmail({
from: `passwordrecover@${process.env.WEB_ADDRESS}`,
to: fields.email,
subject: 'Password Recovery',
text: msg + addr,
html: msgHtml
}, (err, reply) => {
//final check
if (err) {
res.write(`<p>Something went wrong (did you use a valid email?)</p>${err}`)
res.end();
return;
}
res.status(200).write('Recovery email sent!');
res.end();
});
}); });
}); });
}); });
} });
} }
function passwordReset(connection) { const passwordReset = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) {
res.status(400).write('Invalid reset data (invalid email/password)');
res.end();
return;
}
//get the account based on this email, token
let query = 'SELECT * FROM accounts WHERE email = ? AND id IN (SELECT passwordRecover.accountId FROM passwordRecover WHERE token = ?);';
connection.query(query, [fields.email, fields.token], (err, results) => {
if (err) throw err; if (err) throw err;
//validate email, username and password //results should be only 1 account
if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) { if (results.length !== 1) {
res.status(400).write('Invalid reset data (invalid email/password)'); res.status(400).write('Invalid reset data (incorrect parameters/database state)');
res.end(); res.end();
return; return;
} }
//get the account based on this email, token //generate the new salt, hash
let query = 'SELECT * FROM accounts WHERE email = ? AND id IN (SELECT passwordRecover.accountId FROM passwordRecover WHERE token = ?);'; bcrypt.genSalt(11, (err, salt) => {
connection.query(query, [fields.email, fields.token], (err, results) => {
if (err) throw err; if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
//results should be only 1 account
if (results.length !== 1) {
res.status(400).write('Invalid reset data (incorrect parameters/database state)');
res.end();
return;
}
//generate the new salt, hash
bcrypt.genSalt(11, (err, salt) => {
if (err) throw err; if (err) throw err;
bcrypt.hash(fields.password, salt, (err, hash) => {
//update the salt, hash
let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;';
connection.query(query, [salt, hash, fields.email], (err) => {
if (err) throw err; if (err) throw err;
//update the salt, hash //delete the recover request from the database
let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;'; let query = 'DELETE FROM passwordRecover WHERE accountId IN (SELECT id FROM accounts WHERE email = ?);';
connection.query(query, [salt, hash, fields.email], (err) => { connection.query(query, [fields.email], (err) => {
if (err) throw err; if (err) throw err;
//delete the recover request from the database res.status(200).write('Password updated!');
let query = 'DELETE FROM passwordRecover WHERE accountId IN (SELECT id FROM accounts WHERE email = ?);'; res.end();
connection.query(query, [fields.email], (err) => { return;
if (err) throw err;
res.status(200).write('Password updated!');
res.end();
return;
});
}); });
}); });
}); });
}); });
}); });
} });
} }
module.exports = { module.exports = {
+19 -25
View File
@@ -4,19 +4,17 @@ require('dotenv').config();
//libraries //libraries
let formidable = require('formidable'); let formidable = require('formidable');
function profileCreate(connection) { const profileCreate = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err; if (err) throw err;
//separate this section so it can be used elsewhere too //separate this section so it can be used elsewhere too
return profileCreateInner(connection, req, res, fields); return profileCreateInner(connection, req, res, fields);
}); });
};
} }
function profileCreateInner(connection, req, res, fields) { function profileCreateInner(connection, req, res, fields) {
@@ -51,24 +49,20 @@ function profileCreateInner(connection, req, res, fields) {
}); });
} }
function profileRequest(connection) { const profileRequest = (connection) => (req, res) => {
return (req, res) => { //formidable handles forms
//formidable handles forms let form = formidable.IncomingForm();
let form = formidable.IncomingForm();
//parse form //parse form
form.parse(req, (err, fields) => { form.parse(req, (err, fields) => {
if (err) throw err; if (err) throw err;
//separate this section so it can be used elsewhere too //separate this section so it can be used elsewhere too
return profileRequestInner(connection, req, res, fields); return profileRequestInner(connection, req, res, fields);
}); });
}; };
}
function profileRequestInner(connection, req, res, fields) { function profileRequestInner(connection, req, res, fields) {
//TODO: do something with the id and token provided
let query = 'SELECT * FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);'; let query = 'SELECT * FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);';
connection.query(query, [fields.username], (err, results) => { connection.query(query, [fields.username], (err, results) => {
if (err) throw err; if (err) throw err;