2020-01-02 16:58:06 +00:00
/ *
* functions for mail verify
* @ flow
* /
2021-01-30 12:32:46 +00:00
/* eslint-disable max-len */
2020-01-06 10:46:11 +00:00
import nodemailer from 'nodemailer' ;
2020-01-02 16:58:06 +00:00
2020-01-06 10:46:11 +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' ;
2020-04-24 17:46:18 +00:00
import { GMAIL _USER , GMAIL _PW } from './config' ;
2020-01-02 16:58:06 +00:00
import RegUser from '../data/models/RegUser' ;
2020-01-06 10:46:11 +00:00
/ *
* define mail transport
* using unix command sendmail
* /
2020-04-24 17:46:18 +00:00
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' ,
} ) ;
const from = ( GMAIL _USER && GMAIL _PW )
? GMAIL _USER
: 'donotreply@pixelplanet.fun' ;
2020-01-02 16:58:06 +00:00
// TODO make code expire
class MailProvider {
2020-01-06 10:46:11 +00:00
verifyCodes : Object ;
2020-01-02 16:58:06 +00:00
constructor ( ) {
2020-01-06 10:46:11 +00:00
this . clearCodes = this . clearCodes . bind ( this ) ;
2020-01-02 16:58:06 +00:00
2020-01-06 10:46:11 +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 ) ;
2020-01-06 10:46:11 +00:00
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 ,
2020-01-06 10:46:11 +00:00
) ;
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 } ` ) ;
2020-01-06 10:46:11 +00:00
const code = this . setCode ( to ) ;
2020-01-09 14:54:55 +00:00
const verifyUrl = ` ${ host } /api/auth/verify?token= ${ code } ` ;
2020-01-06 10:46:11 +00:00
transporter . sendMail ( {
2020-04-24 17:46:18 +00:00
from ,
2020-01-02 16:58:06 +00:00
to ,
replyTo : 'donotreply@pixelplanet.fun' ,
2021-01-30 12:32:46 +00:00
subject : t ` Welcome ${ name } to PixelPlanet, plese verify your mail ` ,
2020-04-24 17:46:18 +00:00
// 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" / > ` ,
2020-01-06 10:46:11 +00:00
} , ( err ) => {
2020-01-02 16:58:06 +00:00
if ( err ) {
2020-04-24 17:46:18 +00:00
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 ) ;
2020-01-06 10:46:11 +00:00
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 } } ) ;
2020-01-06 10:46:11 +00:00
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 } ` ) ;
2020-01-06 10:46:11 +00:00
const code = this . setCode ( to ) ;
2020-01-09 14:54:55 +00:00
const restoreUrl = ` ${ host } /reset_password?token= ${ code } ` ;
2020-01-06 10:46:11 +00:00
transporter . sendMail ( {
2020-04-24 17:46:18 +00:00
from ,
2020-01-02 16:58:06 +00:00
to ,
replyTo : 'donotreply@pixelplanet.fun' ,
2021-01-30 12:32:46 +00:00
subject : t ` You forgot your password for PixelPlanet? Get a new one here ` ,
2020-04-24 17:46:18 +00:00
// 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" / > ` ,
2020-01-06 10:46:11 +00:00
} , ( err ) => {
2020-01-02 16:58:06 +00:00
if ( err ) {
logger . error ( err & err . stack ) ;
}
} ) ;
return null ;
}
2020-01-06 10:46:11 +00:00
setCode ( email ) {
const code = MailProvider . createCode ( ) ;
this . verifyCodes [ email ] = {
2020-01-02 16:58:06 +00:00
code ,
timestamp : Date . now ( ) ,
} ;
return code ;
}
2020-01-06 10:46:11 +00:00
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
}
}
2020-01-06 10:46:11 +00:00
toDelete . forEach ( ( email ) => {
2020-01-02 16:58:06 +00:00
logger . info ( ` Mail Code for ${ email } expired ` ) ;
2020-01-06 10:46:11 +00:00
delete this . verifyCodes [ email ] ;
2020-01-02 16:58:06 +00:00
} ) ;
}
// Note: code gets deleted on check
2020-01-06 10:46:11 +00:00
checkCode ( code ) {
2020-01-02 16:58:06 +00:00
let email = null ;
2020-01-06 10:46:11 +00:00
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 } . ` ) ;
2020-01-06 10:46:11 +00:00
delete this . verifyCodes [ email ] ;
2020-01-02 16:58:06 +00:00
return email ;
}
async verify ( code ) {
2020-01-06 10:46:11 +00:00
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 ,
} ) ;
2020-04-30 01:20:31 +00:00
return reguser . name ;
2020-01-02 16:58:06 +00:00
}
2020-01-06 10:46:11 +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 } ` ;
}
2020-01-06 10:46:11 +00:00
static cleanUsers ( ) {
2020-01-02 16:58:06 +00:00
// delete users that requier verification for more than 4 days
2020-01-05 23:34:34 +00:00
/ *
2020-01-02 16:58:06 +00:00
RegUser . destroy ( {
where : {
verificationReqAt : {
2020-01-06 10:46:11 +00:00
[ 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-05 23:34:34 +00:00
* /
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 ;