172 lines
4.7 KiB
Scala
172 lines
4.7 KiB
Scala
package lila.security
|
|
|
|
import akka.actor.ActorSystem
|
|
import io.methvin.play.autoconfig._
|
|
import play.api.i18n.Lang
|
|
import play.api.libs.mailer.{ Email, SMTPConfiguration, SMTPMailer }
|
|
import scala.concurrent.duration.{ span => _, _ }
|
|
import scala.concurrent.{ blocking, Future }
|
|
import scalatags.Text.all._
|
|
|
|
import lila.common.config.Secret
|
|
import lila.common.{ Chronometer, EmailAddress, ThreadLocalRandom }
|
|
import lila.common.String.html.{ escapeHtml, nl2brUnsafe }
|
|
import lila.i18n.I18nKeys.{ emails => trans }
|
|
|
|
final class Mailer(
|
|
config: Mailer.Config,
|
|
getSecondaryPercentage: () => Int
|
|
)(implicit
|
|
ec: scala.concurrent.ExecutionContext,
|
|
system: ActorSystem
|
|
) {
|
|
|
|
private val workQueue =
|
|
new lila.hub.DuctSequencer(maxSize = 512, timeout = Mailer.timeout * 2, name = "mailer")
|
|
|
|
private val primaryClient = new SMTPMailer(config.primary.toClientConfig)
|
|
private val secondaryClient = new SMTPMailer(config.secondary.toClientConfig)
|
|
|
|
private def randomClient(): SMTPMailer =
|
|
if (ThreadLocalRandom.nextInt(100) < getSecondaryPercentage()) secondaryClient
|
|
else primaryClient
|
|
|
|
def send(msg: Mailer.Message): Funit =
|
|
if (msg.to.isNoReply) {
|
|
logger.warn(s"Can't send ${msg.subject} to noreply email ${msg.to}")
|
|
funit
|
|
} else
|
|
workQueue {
|
|
Future {
|
|
Chronometer.syncMon(_.email.send.time) {
|
|
blocking {
|
|
randomClient()
|
|
.send(
|
|
Email(
|
|
subject = msg.subject,
|
|
from = config.sender,
|
|
replyTo = Seq(config.sender),
|
|
to = Seq(msg.to.value),
|
|
bodyText = msg.text.some,
|
|
bodyHtml = msg.htmlBody map { body => Mailer.html.wrap(msg.subject, body).render }
|
|
)
|
|
)
|
|
.unit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
object Mailer {
|
|
|
|
private val timeout = 10 seconds
|
|
|
|
case class Smtp(
|
|
mock: Boolean,
|
|
host: String,
|
|
port: Int,
|
|
tls: Boolean,
|
|
user: String,
|
|
password: String
|
|
) {
|
|
def toClientConfig = SMTPConfiguration(
|
|
host = host,
|
|
port = port,
|
|
tlsRequired = tls,
|
|
user = user.some,
|
|
password = password.some,
|
|
mock = mock,
|
|
timeout = Mailer.timeout.toMillis.toInt.some
|
|
)
|
|
}
|
|
implicit val smtpLoader = AutoConfig.loader[Smtp]
|
|
|
|
case class Config(
|
|
primary: Smtp,
|
|
secondary: Smtp,
|
|
sender: String
|
|
)
|
|
implicit val configLoader = AutoConfig.loader[Config]
|
|
|
|
case class Message(
|
|
to: EmailAddress,
|
|
subject: String,
|
|
text: String,
|
|
htmlBody: Option[Frag] = none
|
|
)
|
|
|
|
object txt {
|
|
|
|
def serviceNote(implicit lang: Lang): String = s"""
|
|
${trans.common_note("https://lichess.org").render}
|
|
|
|
${trans.common_contact("https://lichess.org/contact").render}"""
|
|
}
|
|
|
|
object html {
|
|
|
|
val itemscope = attr("itemscope").empty
|
|
val itemtype = attr("itemtype")
|
|
val itemprop = attr("itemprop")
|
|
val emailMessage = div(itemscope, itemtype := "http://schema.org/EmailMessage")
|
|
val pDesc = p(itemprop := "description")
|
|
val potentialAction =
|
|
div(itemprop := "potentialAction", itemscope, itemtype := "http://schema.org/ViewAction")
|
|
def metaName(cont: String) = meta(itemprop := "name", content := cont)
|
|
val publisher = div(itemprop := "publisher", itemscope, itemtype := "http://schema.org/Organization")
|
|
val noteContact = a(itemprop := "url", href := "https://lichess.org/contact")(
|
|
span(itemprop := "name")("lichess.org/contact")
|
|
)
|
|
|
|
def serviceNote(implicit lang: Lang) =
|
|
publisher(
|
|
small(
|
|
trans.common_note(Mailer.html.noteLink),
|
|
" ",
|
|
trans.common_contact(noteContact),
|
|
" ",
|
|
lila.i18n.I18nKeys.readAboutOur(
|
|
a(href := "https://lichess.org/privacy")(
|
|
lila.i18n.I18nKeys.privacyPolicy()
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
def standardEmail(body: String): Frag =
|
|
emailMessage(
|
|
pDesc(nl2brUnsafe(body)),
|
|
publisher
|
|
)
|
|
|
|
val noteLink = a(
|
|
itemprop := "url",
|
|
href := "https://lichess.org/"
|
|
)(span(itemprop := "name")("lichess.org"))
|
|
|
|
def url(u: String)(implicit lang: Lang) =
|
|
frag(
|
|
meta(itemprop := "url", content := u),
|
|
p(a(itemprop := "target", href := u)(u)),
|
|
p(trans.common_orPaste(lang))
|
|
)
|
|
|
|
private[Mailer] def wrap(subject: String, body: Frag): Frag =
|
|
frag(
|
|
raw(s"""<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<title>${escapeHtml(subject)}</title>
|
|
</head>
|
|
<body>"""),
|
|
body,
|
|
raw("""
|
|
</body>
|
|
</html>""")
|
|
)
|
|
}
|
|
}
|