184 lines
4.0 KiB
JavaScript
184 lines
4.0 KiB
JavaScript
//libraries
|
|
const bcrypt = require('bcryptjs');
|
|
const nodemailer = require('nodemailer');
|
|
const Sequelize = require('sequelize');
|
|
const Op = Sequelize.Op;
|
|
|
|
const { accounts, pendingSignups } = require('../database/models');
|
|
|
|
//utilities
|
|
const uuid = require('../utilities/uuid');
|
|
const validateEmail = require('../utilities/validate-email');
|
|
const validateUsername = require('../utilities/validate-username');
|
|
|
|
//auth/signup
|
|
const route = async (req, res) => {
|
|
//validate the given details
|
|
const validateErr = await validateDetails(req.body);
|
|
if (validateErr) {
|
|
return res.status(401).send(validateErr);
|
|
}
|
|
|
|
//script throttle
|
|
const throttle = await checkThrottle(req.body.email);
|
|
if (throttle) {
|
|
console.warn(`Spam attack detected: ${req.body.email} (${req.body.username})`);
|
|
return res.status(401).send(throttle);
|
|
}
|
|
|
|
//generate the password hash
|
|
const hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
|
|
|
|
//generate the validation field
|
|
const token = uuid();
|
|
|
|
//register signup
|
|
const signupErr = await registerPendingSignup(req.body, hash, token);
|
|
if (signupErr) {
|
|
return res.status(500).send(signupErr);
|
|
}
|
|
|
|
//send the validation email
|
|
const emailErr = await sendValidationEmail(req.body.email, req.body.username, token);
|
|
if (emailErr) {
|
|
return res.status(500).send(emailErr);
|
|
}
|
|
|
|
//finally
|
|
res.status(200).send("Validation email sent!");
|
|
return null;
|
|
}
|
|
|
|
const validateDetails = async (body) => {
|
|
//basic formatting
|
|
if (!validateEmail(body.email)) {
|
|
return 'Invalid email';
|
|
}
|
|
|
|
if (!validateUsername(body.username)) {
|
|
return 'Invalid username';
|
|
}
|
|
|
|
//check for existing email
|
|
const emailRecord = await accounts.findOne({
|
|
where: {
|
|
email: body.email
|
|
}
|
|
});
|
|
|
|
if (emailRecord) {
|
|
return 'Email already exists';
|
|
}
|
|
|
|
if (!body.username) {
|
|
return 'Missing username';
|
|
}
|
|
|
|
//check for existing username
|
|
const usernameRecord = await accounts.findOne({
|
|
where: {
|
|
username: body.username
|
|
}
|
|
});
|
|
|
|
if (usernameRecord) {
|
|
return 'Username already exists';
|
|
}
|
|
|
|
//validate password
|
|
if (!body.password) {
|
|
return 'Missing password';
|
|
}
|
|
|
|
if (typeof body.password != "string") {
|
|
return 'Invalid password';
|
|
}
|
|
|
|
if (body.password.length < 8) {
|
|
return 'Password too short';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const checkThrottle = async (email) => {
|
|
//check email delay
|
|
const prev = await pendingSignups.findOne({
|
|
where: {
|
|
email: email,
|
|
}
|
|
});
|
|
|
|
const DateOffset = ( offset ) => { //Thanks, SO!
|
|
return new Date( +new Date + offset );
|
|
}
|
|
|
|
if (!!prev && prev.updatedAt > DateOffset( -5000 )) {
|
|
return "An unknown error occurred";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const registerPendingSignup = async (body, hash, token) => {
|
|
const record = await pendingSignups.upsert({
|
|
email: body.email,
|
|
username: body.username,
|
|
hash: hash,
|
|
contact: body.contact,
|
|
token: token
|
|
});
|
|
|
|
return null;
|
|
};
|
|
|
|
const sendValidationEmail = async (email, username, token) => {
|
|
const addr = `${process.env.WEB_PROTOCOL}://${process.env.WEB_ADDRESS}/auth/validation?username=${username}&token=${token}`;
|
|
const msg =
|
|
`Hello ${username}!\n\n` +
|
|
`Please visit the following link to validate your account: ${addr}\n` +
|
|
(process.env.MAIL_PHYSICAL
|
|
? `\nYou can contact us directly at our physical mailing address here: ${process.env.MAIL_PHYSICAL}\n`
|
|
: ``);
|
|
|
|
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: `signup@${process.env.WEB_ADDRESS}`, //WARNING: google overwrites this
|
|
to: email,
|
|
subject: 'Email Validation',
|
|
text: msg
|
|
});
|
|
}
|
|
catch(e) {
|
|
return `failed to send validation mail: ${e}`;
|
|
}
|
|
|
|
if (info.accepted[0] != email) {
|
|
return 'validation email failed to send';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
module.exports = route;
|
|
|