@ -4,20 +4,17 @@
/* eslint-disable max-len */
import { randomUUID } from 'crypto' ;
import nodemailer from 'nodemailer' ;
import logger from './logger' ;
import { HOUR , MINUTE } from './constants' ;
import { DailyCron , HourlyCron } from '../utils/cron' ;
import { getTTag } from './ttag' ;
import { codeExists , checkCode , setCode } from '../data/redis/mailCodes' ;
import socketEvents from '../socket/socketEvents' ;
import { USE _MAILER , MAIL _ADDRESS } from './config' ;
import { RegUser } from '../data/sql' ;
// TODO make code expire
class MailProvider {
export class MailProvider {
constructor ( ) {
this . enabled = ! ! USE _MAILER ;
if ( this . enabled ) {
@ -26,13 +23,23 @@ class MailProvider {
newline : 'unix' ,
path : '/usr/sbin/sendmail' ,
} ) ;
this . clearCodes = this . clearCodes . bind ( this ) ;
this . verifyCodes = { } ;
HourlyCron . hook ( this . clearCodes ) ;
DailyCron . hook ( MailProvider . cleanUsers ) ;
}
/ *
* mail requests make it through SocketEvents when sharding
* /
socketEvents . on ( 'mail' , ( type , args ) => {
switch ( type ) {
case 'verify' :
this . postVerifyMail ( ... args ) ;
break ;
case 'pwreset' :
this . postPasswdResetMail ( ... args ) ;
break ;
default :
// nothing
}
} ) ;
}
sendMail ( to , subject , html ) {
@ -52,29 +59,10 @@ class MailProvider {
} ) ;
}
sendVerifyMail ( to , name , host , lang ) {
if ( ! this . enabled ) {
return null ;
}
postVerifyMail ( to , name , host , lang , code ) {
const { t } = getTTag ( lang ) ;
const pastMail = this . verifyCodes [ to ] ;
if ( pastMail ) {
const minLeft = Math . floor (
pastMail . timestamp / MINUTE + 2 - Date . now ( ) / MINUTE ,
) ;
if ( minLeft > 0 ) {
logger . info (
` Verify mail for ${ to } - already sent, ${ minLeft } minutes left ` ,
) ;
return t ` 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 . setCode ( to ) ;
const verifyUrl = ` ${ host } /api/auth/verify?token= ${ code } ` ;
const verifyUrl = ` ${ host } /api/auth/verify?token= ${ code } &email= ${ encodeURIComponent ( to ) } ` ;
const subject = t ` Welcome ${ name } to PixelPlanet, plese verify your mail ` ;
const 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 / >
@ -82,27 +70,60 @@ class MailProvider {
$ { t ` Thanks ` } < br / > < br / >
< img alt = "" src = "https://assets.pixelplanet.fun/tile.png" style = "height:64px; width:64px" / > ` ;
this . sendMail ( to , subject , html ) ;
}
async sendVerifyMail ( to , name , host , lang ) {
if ( ! this . enabled && ! socketEvents . isCluster ) {
return null ;
}
const { t } = getTTag ( lang ) ;
const pastCodeAge = await codeExists ( to ) ;
if ( pastCodeAge && pastCodeAge < 180 ) {
const minLeft = Math . ceil ( ( 180 - pastCodeAge ) / 60 ) ;
logger . info (
` Verify mail for ${ to } - already sent, ${ minLeft } minutes left ` ,
) ;
return t ` We already sent you a verification mail, you can request another one in ${ minLeft } minutes. ` ;
}
const code = setCode ( to ) ;
if ( this . enabled ) {
this . postVerifyMail ( to , name , host , lang , code ) ;
} else {
socketEvents . sendMail ( 'verify' , [ to , name , host , lang , code ] ) ;
}
return null ;
}
async sendPasswdResetMail ( to , ip , host , lang ) {
post PasswdResetMail( to , ip , host , lang , code ) {
const { t } = getTTag ( lang ) ;
logger . info ( ` Sending Password reset mail to ${ to } ` ) ;
const restoreUrl = ` ${ host } /reset_password?token= ${ code } ` ;
const subject = t ` You forgot your password for PixelPlanet? Get a new one here ` ;
const 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" / > ` ;
this . sendMail ( to , subject , html ) ;
}
if ( ! this . enabled ) {
async sendPasswdResetMail ( to , ip , host , lang ) {
const { t } = getTTag ( lang ) ;
if ( ! this . enabled && ! socketEvents . isCluster ) {
return t ` Mail is not configured on the server ` ;
}
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 ` ,
) ;
return t ` We already sent you a mail with instructions. Please wait before requesting another mail. ` ;
}
const pastCodeAge = await codeExists ( to ) ;
if ( pastCodeAge && pastCodeAge < 180 ) {
logger . info (
` Password reset mail for ${ to } requested by ${ ip } - already sent ` ,
) ;
return t ` We already sent you a mail with instructions. Please wait before requesting another mail. ` ;
}
const reguser = await RegUser . findOne ( { where : { email : to } } ) ;
if ( pastMail || ! reguser ) {
if ( ! reguser ) {
logger . info (
` Password reset mail for ${ to } requested by ${ ip } - mail not found ` ,
) ;
@ -119,68 +140,20 @@ class MailProvider {
}
* /
logger . info ( ` Sending Password reset mail to ${ to } ` ) ;
const code = this . setCode ( to ) ;
const restoreUrl = ` ${ host } /reset_password?token= ${ code } ` ;
const subject = t ` You forgot your password for PixelPlanet? Get a new one here ` ;
const 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" / > ` ;
this . sendMail ( to , subject , html ) ;
return null ;
}
setCode ( email ) {
const code = MailProvider . createCode ( ) ;
this . verifyCodes [ email ] = {
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 ) ;
}
const code = setCode ( to ) ;
if ( this . enabled ) {
this . postPasswdResetMail ( to , ip , host , lang , code ) ;
} else {
socketEvents . sendMail ( 'pwreset' , [ to , ip , host , lang , code ] ) ;
}
toDelete . forEach ( ( email ) => {
logger . info ( ` Mail Code for ${ email } expired ` ) ;
delete this . verifyCodes [ email ] ;
} ) ;
return null ;
}
// Note: code gets deleted on check
checkCode ( code ) {
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 ) {
email = iteremail ;
break ;
}
}
if ( ! email ) {
logger . info ( ` Mail Code ${ code } not found. ` ) ;
static async verify ( email , code ) {
const ret = await checkCode ( email , code ) ;
if ( ! ret ) {
return false ;
}
logger . info ( ` Got Mail Code from ${ email } . ` ) ;
delete this . verifyCodes [ email ] ;
return email ;
}
async verify ( code ) {
const email = this . checkCode ( code ) ;
if ( ! email ) return false ;
const reguser = await RegUser . findOne ( { where : { email } } ) ;
if ( ! reguser ) {
logger . error ( ` ${ email } does not exist in database ` ) ;
@ -193,13 +166,10 @@ class MailProvider {
return reguser . name ;
}
static createCode ( ) {
return randomUUID ( ) ;
}
/ *
* we do not use this right now
static cleanUsers ( ) {
// delete users that requier verification for more than 4 days
/ *
RegUser . destroy ( {
where : {
verificationReqAt : {
@ -209,8 +179,8 @@ class MailProvider {
verified : 0 ,
} ,
} ) ;
* /
}
* /
}
const mailProvider = new MailProvider ( ) ;