introduce NormalizedEmailAddress

pull/4898/head
Niklas Fiekas 2019-02-19 12:09:44 +01:00
parent 42487a236d
commit c785e35782
10 changed files with 49 additions and 35 deletions

View File

@ -144,7 +144,7 @@ object Account extends LilaController {
fuccess(html.account.email(me, err))
} { data =>
val email = Env.security.emailAddressValidator.validate(data.realEmail) err s"Invalid email ${data.email}"
val newUserEmail = lila.security.EmailConfirm.UserEmail(me.username, email.normalized)
val newUserEmail = lila.security.EmailConfirm.UserEmail(me.username, email.acceptable)
controllers.Auth.EmailConfirmRateLimit(newUserEmail, ctx.req) {
Env.security.emailChange.send(me, newUserEmail.email) inject Redirect {
s"${routes.Account.email}?check=1"

View File

@ -194,9 +194,9 @@ object Auth extends LilaController {
lila.mon.user.register.website()
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString)()
val email = env.emailAddressValidator.validate(data.realEmail) err s"Invalid email ${data.email}"
authLog(data.username, s"${email.normalized} fp: ${data.fingerPrint} mustConfirm: $mustConfirm req:${ctx.req}")
authLog(data.username, s"${email.acceptable} fp: ${data.fingerPrint} mustConfirm: $mustConfirm req:${ctx.req}")
val passwordHash = Env.user.authenticator passEnc ClearPassword(data.password)
UserRepo.create(data.username, passwordHash, email.normalized, ctx.blind, none,
UserRepo.create(data.username, passwordHash, email.acceptable, ctx.blind, none,
mustConfirmEmail = mustConfirm.value)
.flatten(s"No user could be created for ${data.username}")
.map(_ -> email).flatMap {
@ -228,7 +228,7 @@ object Auth extends LilaController {
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString)()
authLog(data.username, s"Signup mobile must confirm email: $mustConfirm")
val passwordHash = Env.user.authenticator passEnc ClearPassword(data.password)
UserRepo.create(data.username, passwordHash, email.normalized, false, apiVersion.some,
UserRepo.create(data.username, passwordHash, email.acceptable, false, apiVersion.some,
mustConfirmEmail = mustConfirm.value)
.flatten(s"No user could be created for ${data.username}")
.map(_ -> email).flatMap {
@ -237,7 +237,7 @@ object Auth extends LilaController {
if (env.emailConfirm.effective) Ok(Json.obj("email_confirm" -> true)).fuccess
else welcome(user, email) >> authenticateUser(user)
}
case (user, _) => welcome(user, email.normalized) >> authenticateUser(user)
case (user, _) => welcome(user, email.acceptable) >> authenticateUser(user)
}
}
)
@ -355,11 +355,10 @@ object Auth extends LilaController {
BadRequest(html.auth.passwordReset(err, captcha, false.some))
},
data => {
val email = data.realEmail.normalize
UserRepo enabledByEmail email flatMap {
case Some(user) => {
UserRepo.enabledWithEmail(data.realEmail.normalize) flatMap {
case Some((user, storedEmail)) => {
lila.mon.user.auth.passwordResetRequest("success")()
Env.security.passwordReset.send(user, email) inject Redirect(routes.Auth.passwordResetSent(data.email))
Env.security.passwordReset.send(user, storedEmail) inject Redirect(routes.Auth.passwordResetSent(storedEmail.conceal))
}
case _ => {
lila.mon.user.auth.passwordResetRequest("no_email")()

View File

@ -147,7 +147,7 @@ object Mod extends LilaController {
err => BadRequest(err.toString).fuccess,
rawEmail => {
val email = Env.security.emailAddressValidator.validate(EmailAddress(rawEmail)) err s"Invalid email ${rawEmail}"
modApi.setEmail(me.id, user.id, email.normalized) inject redirect(user.username, mod = true)
modApi.setEmail(me.id, user.id, email.acceptable) inject redirect(user.username, mod = true)
}
)
}
@ -293,8 +293,8 @@ object Mod extends LilaController {
case _ => fuccess(none)
}
email.?? { em =>
tryWith(em.normalized, em.normalized.value) orElse {
username ?? { tryWith(em.normalized, _) }
tryWith(em.acceptable, em.acceptable.value) orElse {
username ?? { tryWith(em.acceptable, _) }
}
} getOrElse BadRequest(html.mod.emailConfirm(rawQuery, none, none)).fuccess
}

View File

@ -38,5 +38,7 @@ object Iso {
implicit val emailAddressIso = string[EmailAddress](EmailAddress.apply, _.value)
implicit val normalizedEmailAddressIso = string[NormalizedEmailAddress](NormalizedEmailAddress.apply, _.value)
implicit val centisIso = Iso.int[Centis](Centis.apply, _.centis)
}

View File

@ -40,12 +40,14 @@ object IpAddress {
} option IpAddress(str)
}
case class NormalizedEmailAddress(value: String) extends AnyVal with StringValue
case class EmailAddress(value: String) extends AnyVal with StringValue {
def conceal = value split '@' match {
case Array(user, domain) => s"${user take 3}*****@${domain}"
case _ => value
}
def normalize = EmailAddress {
def normalize = NormalizedEmailAddress {
val lower = value.toLowerCase
lower.split('@') match {
case Array(name, domain) if domain == "gmail.com" || domain == "googlemail.com" => {

View File

@ -6,11 +6,11 @@ class EmailTest extends Specification {
"normalize" should {
"handle gmail" in {
EmailAddress("Hello.World+suffix1+suffix2@gmail.com").normalize must_== EmailAddress("helloworld@gmail.com")
EmailAddress("foo.bar@googlemail.com").normalize must_== EmailAddress("foobar@googlemail.com")
EmailAddress("Hello.World+suffix1+suffix2@gmail.com").normalize must_== NormalizedEmailAddress("helloworld@gmail.com")
EmailAddress("foo.bar@googlemail.com").normalize must_== NormalizedEmailAddress("foobar@googlemail.com")
}
"lowercase emails" in {
EmailAddress("aBcDeFG@HOTMAIL.cOM").normalize must_== EmailAddress("abcdefg@hotmail.com")
EmailAddress("aBcDeFG@HOTMAIL.cOM").normalize must_== NormalizedEmailAddress("abcdefg@hotmail.com")
}
}

View File

@ -6,7 +6,7 @@ import reactivemongo.bson._
import scalaz.NonEmptyList
import lila.common.Iso._
import lila.common.{ Iso, IpAddress, EmailAddress }
import lila.common.{ Iso, IpAddress, EmailAddress, NormalizedEmailAddress }
trait Handlers {
@ -73,4 +73,6 @@ trait Handlers {
implicit val ipAddressHandler = isoHandler[IpAddress, String, BSONString](ipAddressIso)
implicit val emailAddressHandler = isoHandler[EmailAddress, String, BSONString](emailAddressIso)
implicit val normalizedEmailAddressHandler = isoHandler[NormalizedEmailAddress, String, BSONString](normalizedEmailAddressIso)
}

View File

@ -20,7 +20,7 @@ final class EmailAddressValidator(
}
def validate(email: EmailAddress): Option[EmailAddressValidator.Acceptable] =
isAcceptable(email) option EmailAddressValidator.Acceptable(email.normalize)
isAcceptable(email) option EmailAddressValidator.Acceptable(email)
/**
* Returns true if an E-mail address is taken by another user.
@ -30,7 +30,7 @@ final class EmailAddressValidator(
* @return
*/
private def isTakenBySomeoneElse(email: EmailAddress, forUser: Option[User]): Boolean =
(lila.user.UserRepo.idByEmail(email) awaitSeconds 2, forUser) match {
(lila.user.UserRepo.idByEmail(email.normalize) awaitSeconds 2, forUser) match {
case (None, _) => false
case (Some(userId), Some(user)) => userId != user.id
case (_, _) => true
@ -42,7 +42,7 @@ final class EmailAddressValidator(
}
def uniqueConstraint(forUser: Option[User]) = Constraint[String]("constraint.email_unique") { e =>
if (isTakenBySomeoneElse(EmailAddress(e).normalize, forUser))
if (isTakenBySomeoneElse(EmailAddress(e), forUser))
Invalid(ValidationError("error.email_unique"))
else Valid
}
@ -82,5 +82,5 @@ final class EmailAddressValidator(
}
object EmailAddressValidator {
case class Acceptable(normalized: EmailAddress)
case class Acceptable(acceptable: EmailAddress)
}

View File

@ -2,7 +2,7 @@ package lila.user
import scala.concurrent.duration._
import lila.common.{ LightUser, EmailAddress }
import lila.common.{ LightUser, EmailAddress, NormalizedEmailAddress }
import lila.rating.PerfType
import org.joda.time.DateTime
@ -161,7 +161,7 @@ object User {
case class Active(user: User)
case class Emails(current: Option[EmailAddress], previous: Option[EmailAddress])
case class Emails(current: Option[EmailAddress], previous: Option[NormalizedEmailAddress])
case class WithEmails(user: User, emails: Emails)
case class ClearPassword(value: String) extends AnyVal {

View File

@ -5,8 +5,7 @@ import reactivemongo.api._
import reactivemongo.api.commands.GetLastError
import reactivemongo.bson._
import lila.common.ApiVersion
import lila.common.EmailAddress
import lila.common.{ ApiVersion, EmailAddress, NormalizedEmailAddress }
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.dsl._
import lila.rating.{ Perf, PerfType }
@ -36,14 +35,12 @@ object UserRepo {
def byIdsSecondary(ids: Iterable[ID]): Fu[List[User]] = coll.byIds[User](ids, ReadPreference.secondaryPreferred)
def byEmail(email: EmailAddress): Fu[Option[User]] = coll.uno[User]($doc(F.email -> email))
def byPrevEmail(email: EmailAddress): Fu[List[User]] = coll.find($doc(F.prevEmail -> email)).list[User]()
def byEmail(email: NormalizedEmailAddress): Fu[Option[User]] = coll.uno[User]($doc(F.email -> email))
def byPrevEmail(email: NormalizedEmailAddress): Fu[List[User]] = coll.find($doc(F.prevEmail -> email)).list[User]()
def idByEmail(email: EmailAddress): Fu[Option[String]] =
def idByEmail(email: NormalizedEmailAddress): Fu[Option[String]] =
coll.primitiveOne[String]($doc(F.email -> email), "_id")
def enabledByEmail(email: EmailAddress): Fu[Option[User]] = byEmail(email) map (_ filter (_.enabled))
def idCursor(
selector: Bdoc,
batchSize: Int = 0,
@ -336,9 +333,21 @@ object UserRepo {
}
def setEmail(id: ID, email: EmailAddress): Funit =
coll.update($id(id), $set(F.email -> email) ++ $unset(F.prevEmail)).void
coll.update($id(id), $set(F.email -> email.normalize) ++ $unset(F.prevEmail)).void
def email(id: ID): Fu[Option[EmailAddress]] = coll.primitiveOne[EmailAddress]($id(id), F.email)
def email(id: ID): Fu[Option[EmailAddress]] = coll.primitiveOne[EmailAddress]($id(id), F.email) // downgrade from normalized (#2509)
def enabledWithEmail(email: NormalizedEmailAddress): Fu[Option[(User, EmailAddress)]] =
coll.find($doc(
F.email -> email,
F.enabled -> true
)).uno[Bdoc].map { maybeDoc =>
for {
doc <- maybeDoc
user = userBSONHandler read doc
storedEmail <- doc.getAs[EmailAddress](F.email) // downgrade
} yield (user, storedEmail)
}
def withEmails(name: String): Fu[Option[User.WithEmails]] =
coll.find($id(normalize(name))).uno[Bdoc].map {
@ -346,8 +355,8 @@ object UserRepo {
User.WithEmails(
userBSONHandler read doc,
User.Emails(
current = doc.getAs[EmailAddress](F.email),
previous = doc.getAs[EmailAddress](F.prevEmail)
current = doc.getAs[EmailAddress](F.email), // downgrade
previous = doc.getAs[NormalizedEmailAddress](F.prevEmail)
)
).some
}
@ -471,7 +480,7 @@ object UserRepo {
$doc(
F.id -> normalize(username),
F.username -> username,
F.email -> email,
F.email -> email.normalize,
F.mustConfirmEmail -> mustConfirmEmail.option(DateTime.now),
F.bpass -> passwordHash,
F.perfs -> $empty,