introduce NormalizedEmailAddress
parent
42487a236d
commit
c785e35782
|
@ -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"
|
||||
|
|
|
@ -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")()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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" => {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue