pixelplanet/src/core/mail.js

226 lines
7.2 KiB
JavaScript
Raw Normal View History

2020-01-02 16:58:06 +00:00
/*
* functions for mail verify
*/
2021-01-30 12:32:46 +00:00
/* eslint-disable max-len */
import nodemailer from 'nodemailer';
2020-01-02 16:58:06 +00:00
import logger from './logger';
2020-01-02 16:58:06 +00:00
import { HOUR, MINUTE } from './constants';
import { DailyCron, HourlyCron } from '../utils/cron';
2021-01-30 12:32:46 +00:00
import { getTTag } from './ttag';
import { GMAIL_USER, GMAIL_PW } from './config';
2020-01-02 16:58:06 +00:00
import RegUser from '../data/models/RegUser';
/*
* define mail transport
* using unix command sendmail
*/
const transporter = (GMAIL_USER && GMAIL_PW)
? nodemailer.createTransport({
service: 'gmail',
auth: {
user: GMAIL_USER,
pass: GMAIL_PW,
},
})
: nodemailer.createTransport({
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail',
});
2022-06-17 10:33:40 +00:00
const address = (GMAIL_USER && GMAIL_PW)
? GMAIL_USER
: 'donotreply@pixelplanet.fun';
2020-01-02 16:58:06 +00:00
// TODO make code expire
class MailProvider {
constructor() {
this.clearCodes = this.clearCodes.bind(this);
2020-01-02 16:58:06 +00:00
this.verifyCodes = {};
HourlyCron.hook(this.clearCodes);
DailyCron.hook(MailProvider.cleanUsers);
2020-01-02 16:58:06 +00:00
}
2021-01-30 12:32:46 +00:00
sendVerifyMail(to, name, host, lang) {
const { t } = getTTag(lang);
const pastMail = this.verifyCodes[to];
if (pastMail) {
const minLeft = Math.floor(
2020-01-13 04:37:12 +00:00
pastMail.timestamp / MINUTE + 2 - Date.now() / MINUTE,
);
if (minLeft > 0) {
logger.info(
`Verify mail for ${to} - already sent, ${minLeft} minutes left`,
);
2021-01-30 12:32:46 +00:00
return t`We already sent you a verification mail, you can request another one in ${minLeft} minutes.`;
2020-01-02 16:58:06 +00:00
}
}
logger.info(`Sending verification mail to ${to} / ${name}`);
const code = this.setCode(to);
const verifyUrl = `${host}/api/auth/verify?token=${code}`;
transporter.sendMail({
2022-06-17 10:33:40 +00:00
from: `PixelPlanet <${address}>`,
2020-01-02 16:58:06 +00:00
to,
2022-06-17 10:33:40 +00:00
replyTo: address,
2021-01-30 12:32:46 +00:00
subject: t`Welcome ${name} to PixelPlanet, plese verify your mail`,
// text: `Hello,\nwelcome to our little community of pixelplacers, to use your account, you have to verify your mail. You can do that here:\n ${verifyUrl} \nHave fun and don't hesitate to contact us if you encouter any problems :)\nThanks`,
2021-01-30 12:32:46 +00:00
html: `<em>${t`Hello ${name}`}</em>,<br />
${t`welcome to our little community of pixelplacers, to use your account, you have to verify your mail. You can do that here: `} <a href="${verifyUrl}">${t`Click to Verify`}</a>. ${t`Or by copying following url:`}<br />${verifyUrl}\n<br />
${t`Have fun and don't hesitate to contact us if you encouter any problems :)`}<br />
${t`Thanks`}<br /><br />
<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`,
}, (err) => {
2020-01-02 16:58:06 +00:00
if (err) {
logger.error(err);
2020-01-02 16:58:06 +00:00
}
});
return null;
}
2021-01-30 12:32:46 +00:00
async sendPasswdResetMail(to, ip, host, lang) {
const { t } = getTTag(lang);
const pastMail = this.verifyCodes[to];
if (pastMail) {
if (Date.now() < pastMail.timestamp + 15 * MINUTE) {
logger.info(
`Password reset mail for ${to} requested by ${ip} - already sent`,
);
2021-01-30 12:32:46 +00:00
return t`We already sent you a mail with instructions. Please wait before requesting another mail.`;
2020-01-02 16:58:06 +00:00
}
}
const reguser = await RegUser.findOne({ where: { email: to } });
if (pastMail || !reguser) {
logger.info(
`Password reset mail for ${to} requested by ${ip} - mail not found`,
);
2021-01-30 12:32:46 +00:00
return t`Couldn't find this mail in our database`;
2020-01-02 16:58:06 +00:00
}
/*
* not sure if this is needed yet
* does it matter if spaming password reset mails or verifications mails?
*
if(!reguser.verified) {
logger.info(`Password reset mail for ${to} requested by ${ip} - mail not verified`);
return "Can't reset password of unverified account.";
}
*/
logger.info(`Sending Password reset mail to ${to}`);
const code = this.setCode(to);
const restoreUrl = `${host}/reset_password?token=${code}`;
transporter.sendMail({
2022-06-17 10:33:40 +00:00
from: `PixelPlanet <${address}>`,
2020-01-02 16:58:06 +00:00
to,
2022-06-17 10:33:40 +00:00
replyTo: address,
2021-01-30 12:32:46 +00:00
subject: t`You forgot your password for PixelPlanet? Get a new one here`,
// text: `Hello,\nYou requested to get a new password. You can change your password within the next 30min here:\n ${restoreUrl} \nHave fun and don't hesitate to contact us if you encouter any problems :)\nIf you did not request this mail, please just ignore it (the ip that requested this mail was ${ip}).\nThanks`,
2021-01-30 12:32:46 +00:00
html: `<em>${t`Hello`}</em>,<br />
${t`You requested to get a new password. You can change your password within the next 30min here: `} <a href="${restoreUrl}">${t`Reset Password`}</a>. ${t`Or by copying following url:`}<br />${restoreUrl}\n<br />
${t`If you did not request this mail, please just ignore it (the ip that requested this mail was ${ip}).`}<br />
${t`Thanks`}<br /><br />\n<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`,
}, (err) => {
2020-01-02 16:58:06 +00:00
if (err) {
logger.error(err & err.stack);
}
});
return null;
}
setCode(email) {
const code = MailProvider.createCode();
this.verifyCodes[email] = {
2020-01-02 16:58:06 +00:00
code,
timestamp: Date.now(),
};
return code;
}
async clearCodes() {
const curTime = Date.now();
const toDelete = [];
const mails = Object.keys(this.verifyCodes);
for (let i = 0; i < mails.length; i += 1) {
const iteremail = mails[i];
if (curTime > this.verifyCodes[iteremail].timestamp + HOUR) {
toDelete.push(iteremail);
2020-01-02 16:58:06 +00:00
}
}
toDelete.forEach((email) => {
2020-01-02 16:58:06 +00:00
logger.info(`Mail Code for ${email} expired`);
delete this.verifyCodes[email];
2020-01-02 16:58:06 +00:00
});
}
// Note: code gets deleted on check
checkCode(code) {
2020-01-02 16:58:06 +00:00
let email = null;
const mails = Object.keys(this.verifyCodes);
for (let i = 0; i < mails.length; i += 1) {
const iteremail = mails[i];
if (this.verifyCodes[iteremail].code === code) {
2020-01-02 16:58:06 +00:00
email = iteremail;
break;
}
}
if (!email) {
logger.info(`Mail Code ${code} not found.`);
return false;
}
logger.info(`Got Mail Code from ${email}.`);
delete this.verifyCodes[email];
2020-01-02 16:58:06 +00:00
return email;
}
async verify(code) {
const email = this.checkCode(code);
2020-01-02 16:58:06 +00:00
if (!email) return false;
const reguser = await RegUser.findOne({ where: { email } });
if (!reguser) {
logger.error(`${email} does not exist in database`);
return false;
}
await reguser.update({
mailVerified: true,
verificationReqAt: null,
});
return reguser.name;
2020-01-02 16:58:06 +00:00
}
static createCode() {
const part1 = Math.random().toString(36).substring(2, 15)
+ Math.random().toString(36).substring(2, 15);
const part2 = Math.random().toString(36).substring(2, 15)
+ Math.random().toString(36).substring(2, 15);
2020-01-02 16:58:06 +00:00
return `${part1}-${part2}`;
}
static cleanUsers() {
2020-01-02 16:58:06 +00:00
// delete users that requier verification for more than 4 days
/*
2020-01-02 16:58:06 +00:00
RegUser.destroy({
where: {
verificationReqAt: {
[Sequelize.Op.lt]:
Sequelize.literal('CURRENT_TIMESTAMP - INTERVAL 4 DAY'),
2020-01-02 16:58:06 +00:00
},
// NOTE: this means that minecraft verified accounts do not get deleted
verified: 0,
},
});
*/
2020-01-02 16:58:06 +00:00
}
}
2020-01-06 11:29:33 +00:00
const mailProvider = new MailProvider();
2020-01-02 16:58:06 +00:00
export default mailProvider;