2020-01-02 16:58:06 +00:00
/ *
* functions for mail verify
* /
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' ;
2022-06-30 09:47:36 +00:00
import { USE _MAILER , MAIL _ADDRESS } from './config' ;
2020-01-02 16:58:06 +00:00
2022-06-19 21:19:10 +00:00
import { RegUser } from '../data/sql' ;
2020-01-02 16:58:06 +00:00
2020-01-06 10:46:11 +00:00
2020-01-02 16:58:06 +00:00
// TODO make code expire
class MailProvider {
constructor ( ) {
2022-06-30 09:47:36 +00:00
this . enabled = ! ! USE _MAILER ;
if ( this . enabled ) {
this . transporter = nodemailer . createTransport ( {
sendmail : true ,
newline : 'unix' ,
path : '/usr/sbin/sendmail' ,
} ) ;
this . clearCodes = this . clearCodes . bind ( this ) ;
this . verifyCodes = { } ;
HourlyCron . hook ( this . clearCodes ) ;
DailyCron . hook ( MailProvider . cleanUsers ) ;
}
}
2020-01-02 16:58:06 +00:00
2022-06-30 09:47:36 +00:00
sendMail ( to , subject , html ) {
if ( ! this . enabled ) {
return ;
}
this . transporter . sendMail ( {
from : ` PixelPlanet < ${ MAIL _ADDRESS } > ` ,
to ,
replyTo : MAIL _ADDRESS ,
subject ,
html ,
} , ( err ) => {
if ( err ) {
logger . error ( err ) ;
}
} ) ;
2020-01-02 16:58:06 +00:00
}
2021-01-30 12:32:46 +00:00
sendVerifyMail ( to , name , host , lang ) {
2022-06-30 09:47:36 +00:00
if ( ! this . enabled ) {
return null ;
}
2021-01-30 12:32:46 +00:00
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
}
}
2022-06-30 09:47:36 +00:00
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 } ` ;
2022-06-30 09:47:36 +00:00
const subject = t ` Welcome ${ name } to PixelPlanet, plese verify your mail ` ;
const html = ` <em> ${ t ` Hello ${ name } ` } </em>,<br />
2021-01-30 12:32:46 +00:00
$ { 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 / >
2022-06-30 09:47:36 +00:00
< img alt = "" src = "https://assets.pixelplanet.fun/tile.png" style = "height:64px; width:64px" / > ` ;
this . sendMail ( to , subject , html ) ;
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 ) ;
2022-06-30 09:47:36 +00:00
if ( ! this . enabled ) {
return t ` Mail is not configured on the server ` ;
}
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
}
2022-06-30 09:47:36 +00:00
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 } ` ;
2022-06-30 09:47:36 +00:00
const subject = t ` You forgot your password for PixelPlanet? Get a new one here ` ;
const html = ` <em> ${ t ` Hello ` } </em>,<br />
2021-01-30 12:32:46 +00:00
$ { 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 / >
2022-06-30 09:47:36 +00:00
$ { t ` Thanks ` } < br / > < br / > \ n < img alt = "" src = "https://assets.pixelplanet.fun/tile.png" style = "height:64px; width:64px" / > ` ;
this . sendMail ( to , subject , html ) ;
2020-01-02 16:58:06 +00:00
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
} ,
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 ;