refactor signup out of the Auth controller
parent
d018e4db77
commit
a3af326fdb
|
@ -3,14 +3,14 @@ package controllers
|
|||
import io.lemonlabs.uri.{ AbsoluteUrl, Url }
|
||||
import com.github.ghik.silencer.silent
|
||||
import ornicar.scalalib.Zero
|
||||
import play.api.data.{ Form, FormError }
|
||||
import play.api.data.FormError
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest }
|
||||
import lila.security.{ EmailAddressValidator, FingerPrint }
|
||||
import lila.common.{ EmailAddress, HTTPRequest }
|
||||
import lila.security.{ FingerPrint, Signup }
|
||||
import lila.user.{ User => UserModel, PasswordHasher }
|
||||
import UserModel.ClearPassword
|
||||
import views._
|
||||
|
@ -171,167 +171,42 @@ final class Auth(
|
|||
}
|
||||
}
|
||||
|
||||
sealed abstract private class MustConfirmEmail(val value: Boolean)
|
||||
private object MustConfirmEmail {
|
||||
|
||||
case object Nope extends MustConfirmEmail(false)
|
||||
case object YesBecausePrintExists extends MustConfirmEmail(true)
|
||||
case object YesBecausePrintMissing extends MustConfirmEmail(true)
|
||||
case object YesBecauseIpExists extends MustConfirmEmail(true)
|
||||
case object YesBecauseIpSusp extends MustConfirmEmail(true)
|
||||
case object YesBecauseMobile extends MustConfirmEmail(true)
|
||||
case object YesBecauseUA extends MustConfirmEmail(true)
|
||||
|
||||
def apply(print: Option[FingerPrint])(implicit ctx: Context): Fu[MustConfirmEmail] = {
|
||||
val ip = HTTPRequest lastRemoteAddress ctx.req
|
||||
api.recentByIpExists(ip) flatMap { ipExists =>
|
||||
if (ipExists) fuccess(YesBecauseIpExists)
|
||||
else if (HTTPRequest weirdUA ctx.req) fuccess(YesBecauseUA)
|
||||
else
|
||||
print.fold[Fu[MustConfirmEmail]](fuccess(YesBecausePrintMissing)) { fp =>
|
||||
api.recentByPrintExists(fp) flatMap { printFound =>
|
||||
if (printFound) fuccess(YesBecausePrintExists)
|
||||
else
|
||||
env.security.ipTrust.isSuspicious(ip).map {
|
||||
case true => YesBecauseIpSusp
|
||||
case _ => Nope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def authLog(user: String, email: String, msg: String) =
|
||||
lila.log("auth").info(s"$user $email $msg")
|
||||
|
||||
private def signupErrLog(err: Form[_])(implicit ctx: Context) =
|
||||
for {
|
||||
username <- err("username").value
|
||||
email <- err("email").value
|
||||
} {
|
||||
authLog(username, email, s"Signup fail: ${Json stringify errorsAsJson(err)}")
|
||||
if (err.errors.exists(_.messages.contains("error.email_acceptable")) &&
|
||||
err("email").value.exists(EmailAddress.matches))
|
||||
authLog(username, email, s"Signup with unacceptable email")
|
||||
}
|
||||
|
||||
def signupPost = OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
NoTor {
|
||||
Firewall {
|
||||
forms.preloadEmailDns >> negotiate(
|
||||
html = forms.signup.website.bindFromRequest.fold(
|
||||
err => {
|
||||
signupErrLog(err)
|
||||
BadRequest(html.auth.signup(err, env.security.recaptchaPublicConfig)).fuccess
|
||||
},
|
||||
data =>
|
||||
env.security.recaptcha.verify(~data.recaptchaResponse, req).flatMap {
|
||||
case false =>
|
||||
authLog(data.username, data.email, "Signup recaptcha fail")
|
||||
BadRequest(
|
||||
html.auth.signup(forms.signup.website fill data, env.security.recaptchaPublicConfig)
|
||||
).fuccess
|
||||
case true =>
|
||||
HasherRateLimit(data.username, ctx.req) {
|
||||
_ =>
|
||||
MustConfirmEmail(data.fingerPrint) flatMap {
|
||||
mustConfirm =>
|
||||
lila.mon.user.register.count(none)
|
||||
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment()
|
||||
val email = env.security.emailAddressValidator
|
||||
.validate(data.realEmail) err s"Invalid email ${data.email}"
|
||||
val passwordHash = env.user.authenticator passEnc ClearPassword(data.password)
|
||||
env.user.repo
|
||||
.create(
|
||||
data.username,
|
||||
passwordHash,
|
||||
email.acceptable,
|
||||
ctx.blind,
|
||||
none,
|
||||
mustConfirmEmail = mustConfirm.value
|
||||
)
|
||||
.orFail(s"No user could be created for ${data.username}")
|
||||
.addEffect { logSignup(_, email.acceptable, data.fingerPrint, none, mustConfirm) }
|
||||
.map(_ -> email)
|
||||
.flatMap {
|
||||
case (user, EmailAddressValidator.Acceptable(email)) if mustConfirm.value =>
|
||||
env.security.emailConfirm.send(user, email) >> {
|
||||
if (env.security.emailConfirm.effective)
|
||||
api.saveSignup(user.id, ctx.mobileApiVersion, data.fingerPrint) inject {
|
||||
Redirect(routes.Auth.checkYourEmail) withCookies
|
||||
lila.security.EmailConfirm.cookie
|
||||
.make(env.lilaCookie, user, email)(ctx.req)
|
||||
} else welcome(user, email) >> redirectNewUser(user)
|
||||
}
|
||||
case (user, EmailAddressValidator.Acceptable(email)) =>
|
||||
welcome(user, email) >> redirectNewUser(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
api = apiVersion =>
|
||||
forms.signup.mobile.bindFromRequest.fold(
|
||||
err => {
|
||||
signupErrLog(err)
|
||||
jsonFormError(err)
|
||||
},
|
||||
data =>
|
||||
HasherRateLimit(data.username, ctx.req) { _ =>
|
||||
val email = env.security.emailAddressValidator
|
||||
.validate(data.realEmail) err s"Invalid email ${data.email}"
|
||||
val mustConfirm = MustConfirmEmail.YesBecauseMobile
|
||||
lila.mon.user.register.count(apiVersion.some)
|
||||
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment()
|
||||
val passwordHash = env.user.authenticator passEnc ClearPassword(data.password)
|
||||
env.user.repo
|
||||
.create(
|
||||
data.username,
|
||||
passwordHash,
|
||||
email.acceptable,
|
||||
false,
|
||||
apiVersion.some,
|
||||
mustConfirmEmail = mustConfirm.value
|
||||
)
|
||||
.orFail(s"No user could be created for ${data.username}")
|
||||
.addEffect { logSignup(_, email.acceptable, none, apiVersion.some, mustConfirm) }
|
||||
.map(_ -> email)
|
||||
.flatMap {
|
||||
case (user, EmailAddressValidator.Acceptable(email)) if mustConfirm.value =>
|
||||
env.security.emailConfirm.send(user, email) >> {
|
||||
if (env.security.emailConfirm.effective)
|
||||
Ok(Json.obj("email_confirm" -> true)).fuccess
|
||||
else welcome(user, email) >> authenticateUser(user)
|
||||
}
|
||||
case (user, _) => welcome(user, email.acceptable) >> authenticateUser(user)
|
||||
}
|
||||
html = env.security.signup
|
||||
.website(ctx.blind)
|
||||
.flatMap {
|
||||
case Signup.RateLimited => limitedDefault.zero.fuccess
|
||||
case Signup.Bad(err) =>
|
||||
BadRequest(html.auth.signup(err, env.security.recaptchaPublicConfig)).fuccess
|
||||
case Signup.ConfirmEmail(user, email) =>
|
||||
fuccess {
|
||||
Redirect(routes.Auth.checkYourEmail) withCookies
|
||||
lila.security.EmailConfirm.cookie
|
||||
.make(env.lilaCookie, user, email)(ctx.req)
|
||||
}
|
||||
)
|
||||
case Signup.AllSet(user, email) => welcome(user, email) >> redirectNewUser(user)
|
||||
},
|
||||
api = apiVersion =>
|
||||
env.security.signup
|
||||
.mobile(apiVersion)
|
||||
.flatMap {
|
||||
case Signup.RateLimited => limitedDefault.zero.fuccess
|
||||
case Signup.Bad(err) => jsonFormError(err)
|
||||
case Signup.ConfirmEmail(_, _) => Ok(Json.obj("email_confirm" -> true)).fuccess
|
||||
case Signup.AllSet(user, email) => welcome(user, email) >> authenticateUser(user)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def logSignup(
|
||||
user: UserModel,
|
||||
email: EmailAddress,
|
||||
fingerPrint: Option[lila.security.FingerPrint],
|
||||
apiVersion: Option[ApiVersion],
|
||||
mustConfirm: MustConfirmEmail
|
||||
)(implicit ctx: Context) = {
|
||||
authLog(
|
||||
user.username,
|
||||
email.value,
|
||||
s"fp: ${fingerPrint} mustConfirm: $mustConfirm fp: ${fingerPrint.??(_.value)} api: ${apiVersion.??(_.value)}"
|
||||
)
|
||||
val ip = HTTPRequest lastRemoteAddress ctx.req
|
||||
env.security.ipTrust.isSuspicious(ip) foreach { susp =>
|
||||
env.slack.api.signup(user, email, ip, fingerPrint.flatMap(_.hash).map(_.value), apiVersion, susp)
|
||||
}
|
||||
}
|
||||
|
||||
private def welcome(user: UserModel, email: EmailAddress)(implicit ctx: Context): Funit = {
|
||||
garbageCollect(user, email)
|
||||
env.security.automaticEmail.welcome(user, email)
|
||||
|
|
|
@ -142,7 +142,7 @@ final class Challenge(
|
|||
10 minute,
|
||||
name = "challenge create per IP",
|
||||
key = "challenge_create_ip",
|
||||
enforce = env.net.rateLimit
|
||||
enforce = env.net.rateLimit.value
|
||||
)
|
||||
|
||||
private val ChallengeUserRateLimit = new lila.memo.RateLimit[lila.user.User.ID](
|
||||
|
@ -150,7 +150,7 @@ final class Challenge(
|
|||
1 minute,
|
||||
name = "challenge create per user",
|
||||
key = "challenge_create_user",
|
||||
enforce = env.net.rateLimit
|
||||
enforce = env.net.rateLimit.value
|
||||
)
|
||||
|
||||
def toFriend(id: String) = AuthBody { implicit ctx => _ =>
|
||||
|
|
|
@ -29,7 +29,7 @@ final class Setup(
|
|||
1 minute,
|
||||
name = "setup post",
|
||||
key = "setup_post",
|
||||
enforce = env.net.rateLimit
|
||||
enforce = env.net.rateLimit.value
|
||||
)
|
||||
|
||||
def aiForm = Open { implicit ctx =>
|
||||
|
|
|
@ -29,6 +29,7 @@ object config {
|
|||
|
||||
case class NetDomain(value: String) extends AnyVal with StringValue
|
||||
case class AssetDomain(value: String) extends AnyVal with StringValue
|
||||
case class RateLimit(value: Boolean) extends AnyVal
|
||||
|
||||
case class NetConfig(
|
||||
domain: NetDomain,
|
||||
|
@ -37,7 +38,7 @@ object config {
|
|||
@ConfigName("asset.domain") assetDomain: AssetDomain,
|
||||
@ConfigName("socket.domains") socketDomains: List[String],
|
||||
crawlable: Boolean,
|
||||
@ConfigName("ratelimit") rateLimit: Boolean,
|
||||
@ConfigName("ratelimit") rateLimit: RateLimit,
|
||||
email: EmailAddress,
|
||||
ip: IpAddress
|
||||
)
|
||||
|
@ -52,6 +53,7 @@ object config {
|
|||
implicit val netDomainLoader = strLoader(NetDomain.apply)
|
||||
implicit val assetDomainLoader = strLoader(AssetDomain.apply)
|
||||
implicit val ipLoader = strLoader(IpAddress.apply)
|
||||
implicit val rateLimitLoader = boolLoader(RateLimit.apply)
|
||||
implicit val netLoader = AutoConfig.loader[NetConfig]
|
||||
|
||||
implicit val strListLoader: ConfigLoader[List[String]] = ConfigLoader { c => k =>
|
||||
|
|
|
@ -4,7 +4,7 @@ import akka.stream.scaladsl._
|
|||
import play.api.libs.json._
|
||||
|
||||
import lila.common.{ Bus, HTTPRequest }
|
||||
import lila.security.Signup
|
||||
import lila.security.UserSignup
|
||||
|
||||
final class ModStream {
|
||||
|
||||
|
@ -12,9 +12,9 @@ final class ModStream {
|
|||
|
||||
private val blueprint =
|
||||
Source
|
||||
.queue[Signup](32, akka.stream.OverflowStrategy.dropHead)
|
||||
.queue[UserSignup](32, akka.stream.OverflowStrategy.dropHead)
|
||||
.map {
|
||||
case Signup(user, email, req, fp, suspIp) =>
|
||||
case UserSignup(user, email, req, fp, suspIp) =>
|
||||
Json.obj(
|
||||
"t" -> "signup",
|
||||
"username" -> user.username,
|
||||
|
@ -31,7 +31,7 @@ final class ModStream {
|
|||
|
||||
def apply(): Source[String, _] = blueprint mapMaterializedValue { queue =>
|
||||
val sub = Bus.subscribeFun(classifier) {
|
||||
case signup: Signup => queue offer signup
|
||||
case signup: UserSignup => queue offer signup
|
||||
}
|
||||
|
||||
queue.watchCompletion dforeach { _ =>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package lila.security
|
||||
|
||||
import scalatags.Text.all._
|
||||
import play.api.i18n.Lang
|
||||
import lila.common.config._
|
||||
import lila.common.EmailAddress
|
||||
import lila.i18n.I18nKeys.{ emails => trans }
|
||||
|
|
|
@ -107,6 +107,8 @@ final class Env(
|
|||
|
||||
lazy val automaticEmail = wire[AutomaticEmail]
|
||||
|
||||
lazy val signup = wire[Signup]
|
||||
|
||||
private lazy val dnsApi: DnsApi = wire[DnsApi]
|
||||
|
||||
private lazy val checkMail: CheckMail = wire[CheckMail]
|
||||
|
|
|
@ -58,7 +58,7 @@ final class GarbageCollector(
|
|||
val printOpt = spy.prints.headOption
|
||||
logger.debug(s"apply ${data.user.username} print=${printOpt}")
|
||||
Bus.publish(
|
||||
lila.security.Signup(user, email, req, printOpt.map(_.value), ipSusp),
|
||||
lila.security.UserSignup(user, email, req, printOpt.map(_.value), ipSusp),
|
||||
"userSignup"
|
||||
)
|
||||
printOpt.map(_.value) filter printBan.blocks match {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package lila.security
|
||||
|
||||
import play.api.i18n.Lang
|
||||
import scala.concurrent.duration._
|
||||
import scalatags.Text.all._
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ final class SecurityApi(
|
|||
def saveSignup(userId: User.ID, apiVersion: Option[ApiVersion], fp: Option[FingerPrint])(
|
||||
implicit req: RequestHeader
|
||||
): Funit = {
|
||||
val sessionId = Random secureString 22
|
||||
val sessionId = Random nextString 22
|
||||
store.save(s"SIG-$sessionId", userId, req, apiVersion, up = false, fp = fp)
|
||||
}
|
||||
|
||||
|
@ -139,10 +139,6 @@ final class SecurityApi(
|
|||
def reqSessionId(req: RequestHeader): Option[String] =
|
||||
req.session.get(sessionIdKey) orElse req.headers.get(sessionIdKey)
|
||||
|
||||
def recentByIpExists(ip: IpAddress): Fu[Boolean] = store recentByIpExists ip
|
||||
|
||||
def recentByPrintExists(fp: FingerPrint): Fu[Boolean] = store recentByPrintExists fp
|
||||
|
||||
def recentUserIdsByFingerHash(fh: FingerHash) = recentUserIdsByField("fp")(fh.value)
|
||||
|
||||
def recentUserIdsByIp(ip: IpAddress) = recentUserIdsByField("ip")(ip.value)
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package lila.security
|
||||
|
||||
import play.api.data._
|
||||
import play.api.i18n.Lang
|
||||
import play.api.mvc.{ Request, RequestHeader }
|
||||
import scala.util.chaining._
|
||||
|
||||
import lila.common.config.NetConfig
|
||||
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest }
|
||||
import lila.user.{ PasswordHasher, User }
|
||||
|
||||
final class Signup(
|
||||
store: Store,
|
||||
api: SecurityApi,
|
||||
ipTrust: IpTrust,
|
||||
forms: DataForm,
|
||||
emailAddressValidator: EmailAddressValidator,
|
||||
emailConfirm: EmailConfirm,
|
||||
recaptcha: Recaptcha,
|
||||
authenticator: lila.user.Authenticator,
|
||||
userRepo: lila.user.UserRepo,
|
||||
slack: lila.slack.SlackApi,
|
||||
netConfig: NetConfig
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
sealed abstract private class MustConfirmEmail(val value: Boolean)
|
||||
private object MustConfirmEmail {
|
||||
|
||||
case object Nope extends MustConfirmEmail(false)
|
||||
case object YesBecausePrintExists extends MustConfirmEmail(true)
|
||||
case object YesBecausePrintMissing extends MustConfirmEmail(true)
|
||||
case object YesBecauseIpExists extends MustConfirmEmail(true)
|
||||
case object YesBecauseIpSusp extends MustConfirmEmail(true)
|
||||
case object YesBecauseMobile extends MustConfirmEmail(true)
|
||||
case object YesBecauseUA extends MustConfirmEmail(true)
|
||||
|
||||
def apply(print: Option[FingerPrint])(implicit req: RequestHeader): Fu[MustConfirmEmail] = {
|
||||
val ip = HTTPRequest lastRemoteAddress req
|
||||
store.recentByIpExists(ip) flatMap { ipExists =>
|
||||
if (ipExists) fuccess(YesBecauseIpExists)
|
||||
else if (HTTPRequest weirdUA req) fuccess(YesBecauseUA)
|
||||
else
|
||||
print.fold[Fu[MustConfirmEmail]](fuccess(YesBecausePrintMissing)) { fp =>
|
||||
store.recentByPrintExists(fp) flatMap { printFound =>
|
||||
if (printFound) fuccess(YesBecausePrintExists)
|
||||
else
|
||||
ipTrust.isSuspicious(ip).map {
|
||||
case true => YesBecauseIpSusp
|
||||
case _ => Nope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def website(blind: Boolean)(implicit req: Request[_], lang: Lang): Fu[Signup.Result] =
|
||||
forms.signup.website.bindFromRequest.fold[Fu[Signup.Result]](
|
||||
err => fuccess(Signup.Bad(err tap signupErrLog)),
|
||||
data =>
|
||||
recaptcha.verify(~data.recaptchaResponse, req).flatMap {
|
||||
case false =>
|
||||
authLog(data.username, data.email, "Signup recaptcha fail")
|
||||
fuccess(Signup.Bad(forms.signup.website fill data))
|
||||
case true =>
|
||||
HasherRateLimit(data.username, req) {
|
||||
_ =>
|
||||
MustConfirmEmail(data.fingerPrint) flatMap {
|
||||
mustConfirm =>
|
||||
lila.mon.user.register.count(none)
|
||||
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment()
|
||||
val email = emailAddressValidator
|
||||
.validate(data.realEmail) err s"Invalid email ${data.email}"
|
||||
val passwordHash = authenticator passEnc User.ClearPassword(data.password)
|
||||
userRepo
|
||||
.create(
|
||||
data.username,
|
||||
passwordHash,
|
||||
email.acceptable,
|
||||
blind,
|
||||
none,
|
||||
mustConfirmEmail = mustConfirm.value
|
||||
)
|
||||
.orFail(s"No user could be created for ${data.username}")
|
||||
.addEffect { logSignup(req, _, email.acceptable, data.fingerPrint, none, mustConfirm) }
|
||||
.flatMap {
|
||||
confirmOrAllSet(email, mustConfirm, data.fingerPrint, none)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private def confirmOrAllSet(
|
||||
email: EmailAddressValidator.Acceptable,
|
||||
mustConfirm: MustConfirmEmail,
|
||||
fingerPrint: Option[FingerPrint],
|
||||
apiVersion: Option[ApiVersion]
|
||||
)(user: User)(implicit req: RequestHeader, lang: Lang): Fu[Signup.Result] =
|
||||
if (mustConfirm.value) {
|
||||
emailConfirm.send(user, email.acceptable) >> {
|
||||
if (emailConfirm.effective)
|
||||
api.saveSignup(user.id, apiVersion, fingerPrint) inject
|
||||
Signup.ConfirmEmail(user, email.acceptable)
|
||||
else fuccess(Signup.AllSet(user, email.acceptable))
|
||||
}
|
||||
} else fuccess(Signup.AllSet(user, email.acceptable))
|
||||
|
||||
def mobile(
|
||||
apiVersion: ApiVersion
|
||||
)(implicit req: Request[_], lang: Lang): Fu[Signup.Result] =
|
||||
forms.signup.mobile.bindFromRequest.fold[Fu[Signup.Result]](
|
||||
err => fuccess(Signup.Bad(err tap signupErrLog)),
|
||||
data =>
|
||||
HasherRateLimit(data.username, req) { _ =>
|
||||
val email = emailAddressValidator
|
||||
.validate(data.realEmail) err s"Invalid email ${data.email}"
|
||||
val mustConfirm = MustConfirmEmail.YesBecauseMobile
|
||||
lila.mon.user.register.count(apiVersion.some)
|
||||
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment()
|
||||
val passwordHash = authenticator passEnc User.ClearPassword(data.password)
|
||||
userRepo
|
||||
.create(
|
||||
data.username,
|
||||
passwordHash,
|
||||
email.acceptable,
|
||||
false,
|
||||
apiVersion.some,
|
||||
mustConfirmEmail = mustConfirm.value
|
||||
)
|
||||
.orFail(s"No user could be created for ${data.username}")
|
||||
.addEffect { logSignup(req, _, email.acceptable, none, apiVersion.some, mustConfirm) }
|
||||
.flatMap {
|
||||
confirmOrAllSet(email, mustConfirm, none, apiVersion.some)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
implicit private val ResultZero = ornicar.scalalib.Zero.instance[Signup.Result](Signup.RateLimited)
|
||||
|
||||
private def HasherRateLimit =
|
||||
PasswordHasher.rateLimit[Signup.Result](enforce = netConfig.rateLimit) _
|
||||
|
||||
private def logSignup(
|
||||
req: RequestHeader,
|
||||
user: User,
|
||||
email: EmailAddress,
|
||||
fingerPrint: Option[FingerPrint],
|
||||
apiVersion: Option[ApiVersion],
|
||||
mustConfirm: MustConfirmEmail
|
||||
) = {
|
||||
authLog(
|
||||
user.username,
|
||||
email.value,
|
||||
s"fp: ${fingerPrint} mustConfirm: $mustConfirm fp: ${fingerPrint.??(_.value)} api: ${apiVersion.??(_.value)}"
|
||||
)
|
||||
val ip = HTTPRequest lastRemoteAddress req
|
||||
ipTrust.isSuspicious(ip) foreach { susp =>
|
||||
slack.signup(user, email, ip, fingerPrint.flatMap(_.hash).map(_.value), apiVersion, susp)
|
||||
}
|
||||
}
|
||||
|
||||
private def signupErrLog(err: Form[_]) =
|
||||
for {
|
||||
username <- err("username").value
|
||||
email <- err("email").value
|
||||
} {
|
||||
if (err.errors.exists(_.messages.contains("error.email_acceptable")) &&
|
||||
err("email").value.exists(EmailAddress.matches))
|
||||
authLog(username, email, s"Signup with unacceptable email")
|
||||
}
|
||||
|
||||
private def authLog(user: String, email: String, msg: String) =
|
||||
lila.log("auth").info(s"$user $email $msg")
|
||||
}
|
||||
|
||||
object Signup {
|
||||
|
||||
sealed trait Result
|
||||
case class Bad(err: Form[_]) extends Result
|
||||
case object RateLimited extends Result
|
||||
case class ConfirmEmail(user: User, email: EmailAddress) extends Result
|
||||
case class AllSet(user: User, email: EmailAddress) extends Result
|
||||
}
|
|
@ -31,7 +31,7 @@ case class RecaptchaPublicConfig(key: String, enabled: Boolean)
|
|||
|
||||
case class LameNameCheck(value: Boolean) extends AnyVal
|
||||
|
||||
case class Signup(
|
||||
case class UserSignup(
|
||||
user: User,
|
||||
email: EmailAddress,
|
||||
req: RequestHeader,
|
||||
|
|
|
@ -110,9 +110,9 @@ object PasswordHasher {
|
|||
)
|
||||
|
||||
def rateLimit[A: Zero](
|
||||
enforce: Boolean
|
||||
enforce: lila.common.config.RateLimit
|
||||
)(username: String, req: RequestHeader)(run: RateLimit.Charge => Fu[A]): Fu[A] =
|
||||
if (enforce) {
|
||||
if (enforce.value) {
|
||||
val cost = 1
|
||||
val ip = HTTPRequest lastRemoteAddress req
|
||||
rateLimitPerUser(User normalize username, cost = cost) {
|
||||
|
|
Loading…
Reference in New Issue