change from sendmail to nodemailer

fix eslint errors corresponding to mail sending
This commit is contained in:
HF 2020-01-06 11:46:11 +01:00
parent a8987604f3
commit cb5f4b6ef4
9 changed files with 111 additions and 184 deletions

View File

@ -41,10 +41,11 @@ npm run build
All needed files to run it got created in `./build`
#### Note:
If you run into problems, make sure that you have rights to g++ (if not, run as root and then chown username:username -R . after build)
Notes:
If `npm install` fails with "unable to connect to github.com" set:
- If you run into problems, make sure that you have rights to g++ (if not, run as root and then chown username:username -R . after build)
- If `npm install` fails with "unable to connect to github.com" set:
```
git config --global url.https://github.com/.insteadOf git://github.com/
@ -54,7 +55,7 @@ git config --global url.https://github.com/.insteadOf git://github.com/
### Requirements
- nodejs environment with [npm](https://www.npmjs.com/get-npm)
- [pm2](https://github.com/Unitech/pm2) (`npm install -g pm2`) as process manager and for logging
- [redis](https://redis.io/) as database for storgìng the canvas
- [redis](https://redis.io/) as database for storìng the canvas
- mysql or mariadb ([setup own user](https://www.digitalocean.com/community/tutorials/how-to-create-a-new-user-and-grant-permissions-in-mysql) and [create database](https://www.w3schools.com/SQl/sql_create_db.asp) for pixelplanet) for storing additional data like IP blacklist
### Configuration
@ -108,7 +109,7 @@ Notes:
| REDDIT_CLIENT_ID | Media |
| REDDIT_CLIENT_SECRET | Accounts |
Note:
Notes:
- The HTML for SocialMedia logins is in src/componets/UserAreaModal.js , delete stuff from there if you don't need it
- The HTML for the Help Screen is in src/components/HelpModal.js
@ -124,20 +125,28 @@ The default configuration values can be seen in `src/core/config.js` and for the
1. Make sure that mysql and redis are running
3. Start with
```
pm2 start ecosystem.yml
```
Note: It might be neccessary to change the charset and collate of the sql colum names of table Users to support special character names, which can be done with the SQL command:
Notes:
- pixelplanet uses the unix command sendmail for sending verification and password reset mails. If you are on windows, this might not work.
- It might be neccessary to change the charset and collate of the sql colum names of table Users to support special character names, which can be done with the SQL command:
```
ALTER TABLE Users CONVERT TO CHARACTER SET utf8mb4 COLLATE 'utf8mb4_unicode_ci';
```
### Logging
logs are in ~/pm2/log/, you can view them with
```
pm2 log web
```
you can flush the logs with
```
pm2 log flush
```

121
package-lock.json generated
View File

@ -1543,11 +1543,6 @@
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
"dev": true
},
"addressparser": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz",
"integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y="
},
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
@ -2586,36 +2581,6 @@
"node-gyp-build": "~3.7.0"
}
},
"buildmail": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/buildmail/-/buildmail-3.10.0.tgz",
"integrity": "sha1-xoJtcW55RbtvaxQ0tTmF4CmgMVk=",
"requires": {
"addressparser": "1.0.1",
"libbase64": "0.1.0",
"libmime": "2.1.0",
"libqp": "1.1.0",
"nodemailer-fetch": "1.6.0",
"nodemailer-shared": "1.1.0"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz",
"integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI="
},
"libmime": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz",
"integrity": "sha1-Ubx23iKDFh65BRxLyArtcT5P0c0=",
"requires": {
"iconv-lite": "0.4.13",
"libbase64": "0.1.0",
"libqp": "1.1.0"
}
}
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
@ -3996,14 +3961,6 @@
"randombytes": "^2.0.0"
}
},
"dkim-signer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dkim-signer/-/dkim-signer-0.2.2.tgz",
"integrity": "sha1-qoHsBx7u02IngbqpIgRNeADl8wg=",
"requires": {
"libmime": "^2.0.3"
}
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -7251,33 +7208,6 @@
"type-check": "~0.3.2"
}
},
"libbase64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz",
"integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY="
},
"libmime": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.3.tgz",
"integrity": "sha1-JQF8pataHpiq2+JyUBfPHUikKgw=",
"requires": {
"iconv-lite": "0.4.15",
"libbase64": "0.1.0",
"libqp": "1.1.0"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.15",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz",
"integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es="
}
}
},
"libqp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz",
"integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g="
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
@ -7482,32 +7412,6 @@
}
}
},
"mailcomposer": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.12.0.tgz",
"integrity": "sha1-nF4RiKqOHGLsi4a9Q0aBArY56Pk=",
"requires": {
"buildmail": "3.10.0",
"libmime": "2.1.0"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz",
"integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI="
},
"libmime": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz",
"integrity": "sha1-Ubx23iKDFh65BRxLyArtcT5P0c0=",
"requires": {
"iconv-lite": "0.4.13",
"libbase64": "0.1.0",
"libqp": "1.1.0"
}
}
}
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@ -8201,18 +8105,10 @@
"true-case-path": "^1.0.2"
}
},
"nodemailer-fetch": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz",
"integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q="
},
"nodemailer-shared": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz",
"integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=",
"requires": {
"nodemailer-fetch": "1.6.0"
}
"nodemailer": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.2.tgz",
"integrity": "sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ=="
},
"noop-logger": {
"version": "0.1.1",
@ -10158,15 +10054,6 @@
}
}
},
"sendmail": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/sendmail/-/sendmail-1.6.1.tgz",
"integrity": "sha512-lIhvnjSi5e5jL8wA1GPP6j2QVlx6JOEfmdn0QIfmuJdmXYGmJ375kcOU0NSm/34J+nypm4sa1AXrYE5w3uNIIA==",
"requires": {
"dkim-signer": "0.2.2",
"mailcomposer": "3.12.0"
}
},
"seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",

View File

@ -50,6 +50,7 @@
"multer": "^1.4.1",
"mysql2": "^2.1.0",
"node-sass": "^4.11.0",
"nodemailer": "^6.4.2",
"passport": "^0.4.0",
"passport-discord": "^0.1.2",
"passport-facebook": "^3.0.0",
@ -72,7 +73,6 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.2.0",
"sendmail": "^1.6.1",
"sequelize": "^5.19.2",
"sharp": "^0.23.4",
"startaudiocontext": "^1.2.1",

View File

@ -3,50 +3,66 @@
* @flow
*/
// must use require for arguments
import Sequelize from 'sequelize';
import logger from './logger';
import nodemailer from 'nodemailer';
import logger from './logger';
import { HOUR, MINUTE } from './constants';
import { HOSTURL } from './config';
import { DailyCron, HourlyCron } from '../utils/cron';
import RegUser from '../data/models/RegUser';
const sendmail = require('sendmail')({ silent: true });
/*
* define mail transport
* using unix command sendmail
*/
const transporter = nodemailer.createTransport({
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail',
});
// TODO make code expire
class MailProvider {
verify_codes: Object;
verifyCodes: Object;
constructor() {
this.clear_codes = this.clear_codes.bind(this);
this.clearCodes = this.clearCodes.bind(this);
this.verify_codes = {};
HourlyCron.hook(this.clear_codes);
DailyCron.hook(MailProvider.clean_users);
this.verifyCodes = {};
HourlyCron.hook(this.clearCodes);
DailyCron.hook(MailProvider.cleanUsers);
}
send_verify_mail(to, name) {
const past_mail = this.verify_codes[to];
if (past_mail) {
const min_left = Math.floor(past_mail.timestamp / MINUTE + 15 - Date.now() / MINUTE);
if (min_left > 0) {
logger.info(`Verify mail for ${to} - already sent, ${min_left} minutes left`);
return `We already sent you a verification mail, you can request another one in ${min_left} minutes.`;
sendVerifyMail(to, name) {
const pastMail = this.verifyCodes[to];
if (pastMail) {
const minLeft = Math.floor(
pastMail.timestamp / MINUTE + 15 - Date.now() / MINUTE,
);
if (minLeft > 0) {
logger.info(
`Verify mail for ${to} - already sent, ${minLeft} minutes left`,
);
// eslint-disable-next-line max-len
return `We already sent you a verification mail, you can request another one in ${minLeft} minutes.`;
}
}
logger.info(`Sending verification mail to ${to} / ${name}`);
const code = this.set_code(to);
const verify_url = `${HOSTURL}/api/auth/verify?token=${code}`;
sendmail({
const code = this.setCode(to);
const verifyUrl = `${HOSTURL}/api/auth/verify?token=${code}`;
transporter.sendMail({
from: 'donotreply@pixelplanet.fun',
to,
replyTo: 'donotreply@pixelplanet.fun',
// eslint-disable-next-line max-len
subject: `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 ${verify_url} \nHave fun and don't hesitate to contact us if you encouter any problems :)\nThanks`,
}, (err, reply) => {
// eslint-disable-next-line max-len
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`,
}, (err) => {
if (err) {
logger.error(err & err.stack);
}
@ -54,17 +70,22 @@ class MailProvider {
return null;
}
async send_passd_reset_mail(to, ip) {
const past_mail = this.verify_codes[to];
if (past_mail) {
if (Date.now() < past_mail.timestamp + 15 * MINUTE) {
logger.info(`Password reset mail for ${to} requested by ${ip} - already sent`);
async sendPasswdResetMail(to, ip) {
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`,
);
// eslint-disable-next-line max-len
return 'We already sent you a mail with instructions. Please wait before requesting another mail.';
}
}
const reguser = await RegUser.findOne({ where: { email: to } });
if (past_mail || !reguser) {
logger.info(`Password reset mail for ${to} requested by ${ip} - mail not found`);
if (pastMail || !reguser) {
logger.info(
`Password reset mail for ${to} requested by ${ip} - mail not found`,
);
return "Couldn't find this mail in our database";
}
/*
@ -78,15 +99,17 @@ class MailProvider {
*/
logger.info(`Sending Password reset mail to ${to}`);
const code = this.set_code(to);
const restore_url = `${HOSTURL}/reset_password?token=${code}`;
sendmail({
const code = this.setCode(to);
const restoreUrl = `${HOSTURL}/reset_password?token=${code}`;
transporter.sendMail({
from: 'donotreply@pixelplanet.fun',
to,
replyTo: 'donotreply@pixelplanet.fun',
// eslint-disable-next-line max-len
subject: '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 ${restore_url} \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`,
}, (err, reply) => {
// eslint-disable-next-line max-len
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`,
}, (err) => {
if (err) {
logger.error(err & err.stack);
}
@ -94,34 +117,39 @@ class MailProvider {
return null;
}
set_code(email) {
const code = MailProvider.create_code();
this.verify_codes[email] = {
setCode(email) {
const code = MailProvider.createCode();
this.verifyCodes[email] = {
code,
timestamp: Date.now(),
};
return code;
}
async clear_codes() {
const cur_time = Date.now();
const to_delete = [];
for (const iteremail in this.verify_codes) {
if (cur_time > this.verify_codes[iteremail].timestamp + HOUR) {
to_delete.push(iteremail);
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);
}
}
to_delete.forEach((email) => {
toDelete.forEach((email) => {
logger.info(`Mail Code for ${email} expired`);
delete this.verify_codes[email];
delete this.verifyCodes[email];
});
}
// Note: code gets deleted on check
check_code(code) {
checkCode(code) {
let email = null;
for (const iteremail in this.verify_codes) {
if (this.verify_codes[iteremail].code == code) {
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) {
email = iteremail;
break;
}
@ -131,12 +159,12 @@ class MailProvider {
return false;
}
logger.info(`Got Mail Code from ${email}.`);
delete this.verify_codes[email];
delete this.verifyCodes[email];
return email;
}
async verify(code) {
const email = this.check_code(code);
const email = this.checkCode(code);
if (!email) return false;
const reguser = await RegUser.findOne({ where: { email } });
@ -151,18 +179,21 @@ class MailProvider {
return true;
}
static create_code() {
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);
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);
return `${part1}-${part2}`;
}
static clean_users() {
static cleanUsers() {
// delete users that requier verification for more than 4 days
RegUser.destroy({
where: {
verificationReqAt: {
[Sequelize.Op.lt]: Sequelize.literal('CURRENT_TIMESTAMP - INTERVAL 4 DAY'),
[Sequelize.Op.lt]:
Sequelize.literal('CURRENT_TIMESTAMP - INTERVAL 4 DAY'),
},
// NOTE: this means that minecraft verified accounts do not get deleted
verified: 0,

View File

@ -55,7 +55,7 @@ export default async (req: Request, res: Response) => {
mailVerified: false,
});
mailProvider.send_verify_mail(email, user.regUser.name);
mailProvider.sendVerifyMail(email, user.regUser.name);
res.json({
success: true,

View File

@ -70,7 +70,7 @@ export default async (req: Request, res: Response) => {
});
return;
}
mailProvider.send_verify_mail(email, name);
mailProvider.sendVerifyMail(email, name);
res.status(200);
res.json({
success: true,

View File

@ -26,7 +26,7 @@ export default async (req: Request, res: Response) => {
return;
}
const error = mailProvider.send_verify_mail(email, name);
const error = mailProvider.sendVerifyMail(email, name);
if (error) {
res.status(400);
res.json({

View File

@ -28,7 +28,7 @@ export default async (req: Request, res: Response) => {
});
return;
}
const error = await mailProvider.send_passd_reset_mail(email, ip);
const error = await mailProvider.sendPasswdResetMail(email, ip);
if (error) {
res.status(400);
res.json({

View File

@ -54,7 +54,7 @@ router.post('/', async (req: Request, res: Response, next) => {
return;
}
const email = mailProvider.check_code(code);
const email = mailProvider.checkCode(code);
if (!email) {
const html = getPasswordResetHtml(null, null, "This password-reset link isn't valid anymore :(");
res.status(401).send(html);
@ -94,14 +94,14 @@ router.get('/', async (req: Request, res: Response, next) => {
return;
}
const email = mailProvider.check_code(token);
const email = mailProvider.checkCode(token);
if (!email) {
const html = getPasswordResetHtml(null, null, 'This passwort reset link is wrong or already expired, please request a new one (Note: you can use those links just once)');
res.status(401).send(html);
return;
}
const code = mailProvider.set_code(email);
const code = mailProvider.setCode(email);
const html = getPasswordResetHtml(email, code);
res.status(200).send(html);
});