replace recaptcha with hcaptcha - closes #3530
parent
2c95bc03c1
commit
9c34d433f9
|
@ -104,6 +104,6 @@ Lichess as deployed on https://lichess.org/ also uses these external services:
|
|||
- [detectlanguage.com](https://detectlanguage.com/)
|
||||
- Fallback to [Google Fonts](https://fonts.google.com/)
|
||||
- [Google Cloud Messaging](https://developers.google.com/cloud-messaging/) for mobile notifications
|
||||
- [reCAPTCHA](https://www.google.com/recaptcha/)
|
||||
- [hCaptcha](https://hcaptcha.com)
|
||||
- [PeerJS](https://peerjs.com/) for voice chat
|
||||
- [crowdin](https://crowdin.com/project/lichess) for localization
|
||||
|
|
|
@ -391,8 +391,8 @@ final class Account(
|
|||
def reopenApply =
|
||||
OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
env.security.recaptcha.verify() flatMap {
|
||||
_.ok ?? {
|
||||
env.security.hcaptcha.verify() flatMap { captcha =>
|
||||
if (captcha.ok)
|
||||
env.security.forms.reopen.form
|
||||
.bindFromRequest()
|
||||
.fold(
|
||||
|
@ -412,7 +412,7 @@ final class Account(
|
|||
}(rateLimitedFu)
|
||||
}
|
||||
)
|
||||
}
|
||||
else BadRequest(renderReopen(none, none)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -335,8 +335,8 @@ final class Auth(
|
|||
def passwordResetApply =
|
||||
OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
env.security.recaptcha.verify() flatMap {
|
||||
_.ok ?? {
|
||||
env.security.hcaptcha.verify() flatMap { captcha =>
|
||||
if (captcha.ok)
|
||||
forms.passwordReset.form
|
||||
.bindFromRequest()
|
||||
.fold(
|
||||
|
@ -353,7 +353,7 @@ final class Auth(
|
|||
Redirect(routes.Auth.passwordResetSent(data.realEmail.conceal)).fuccess
|
||||
}
|
||||
)
|
||||
}
|
||||
else BadRequest(renderPasswordReset(none, fail = true)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,8 +416,8 @@ final class Auth(
|
|||
def magicLinkApply =
|
||||
OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
env.security.recaptcha.verify() flatMap {
|
||||
_.ok ?? {
|
||||
env.security.hcaptcha.verify() flatMap { captcha =>
|
||||
if (captcha.ok)
|
||||
forms.magicLink.form
|
||||
.bindFromRequest()
|
||||
.fold(
|
||||
|
@ -437,7 +437,8 @@ final class Auth(
|
|||
Redirect(routes.Auth.magicLinkSent(data.realEmail.value)).fuccess
|
||||
}
|
||||
)
|
||||
}
|
||||
else
|
||||
BadRequest(renderMagicLink(none, fail = true)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,14 @@ import controllers.routes
|
|||
|
||||
object reopen {
|
||||
|
||||
def form(form: lila.security.RecaptchaForm[_], error: Option[String] = None)(implicit
|
||||
def form(form: lila.security.HcaptchaForm[_], error: Option[String] = None)(implicit
|
||||
ctx: Context
|
||||
) =
|
||||
views.html.base.layout(
|
||||
title = "Reopen your account",
|
||||
moreCss = cssTag("auth"),
|
||||
moreJs = views.html.base.recaptcha.script(form),
|
||||
csp = defaultCsp.withRecaptcha.some
|
||||
moreJs = views.html.base.hcaptcha.script(form),
|
||||
csp = defaultCsp.withHcaptcha.some
|
||||
) {
|
||||
main(cls := "page-small box box-pad")(
|
||||
h1("Reopen your account"),
|
||||
|
@ -26,7 +26,7 @@ object reopen {
|
|||
p(strong("This will only work once.")),
|
||||
p("If you close your account a second time, there will be no way of recovering it."),
|
||||
hr,
|
||||
postForm(id := form.formId, cls := "form3", action := routes.Account.reopenApply)(
|
||||
postForm(cls := "form3", action := routes.Account.reopenApply)(
|
||||
error.map { err =>
|
||||
p(cls := "error")(strong(err))
|
||||
},
|
||||
|
@ -35,7 +35,8 @@ object reopen {
|
|||
.group(form("email"), trans.email(), help = frag("Email address associated to the account").some)(
|
||||
form3.input(_, typ = "email")
|
||||
),
|
||||
form3.action(views.html.base.recaptcha.button(form)(form3.submit(trans.emailMeALink())))
|
||||
views.html.base.hcaptcha.tag(form),
|
||||
form3.action(form3.submit(trans.emailMeALink()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import play.api.data.{ Field, Form }
|
|||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.security.RecaptchaForm
|
||||
import lila.security.HcaptchaForm
|
||||
import lila.user.User
|
||||
|
||||
object bits {
|
||||
|
@ -33,25 +33,24 @@ object bits {
|
|||
}
|
||||
)
|
||||
|
||||
def passwordReset(form: RecaptchaForm[_], fail: Boolean)(implicit ctx: Context) =
|
||||
def passwordReset(form: HcaptchaForm[_], fail: Boolean)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.passwordReset.txt(),
|
||||
moreCss = cssTag("auth"),
|
||||
moreJs = views.html.base.recaptcha.script(form),
|
||||
csp = defaultCsp.withRecaptcha.some
|
||||
moreJs = views.html.base.hcaptcha.script(form),
|
||||
csp = defaultCsp.withHcaptcha.some
|
||||
) {
|
||||
main(cls := "auth auth-signup box box-pad")(
|
||||
h1(
|
||||
fail option span(cls := "is-red", dataIcon := "L"),
|
||||
trans.passwordReset()
|
||||
),
|
||||
postForm(id := form.formId, cls := "form3", action := routes.Auth.passwordResetApply)(
|
||||
form3.group(form("email"), trans.email())(form3.input(_, typ = "email")(autofocus)),
|
||||
form3.action(
|
||||
views.html.base.recaptcha.button(form) {
|
||||
form3.submit(trans.emailMeALink())
|
||||
}
|
||||
)
|
||||
postForm(cls := "form3", action := routes.Auth.passwordResetApply)(
|
||||
form3.group(form("email"), trans.email())(
|
||||
form3.input(_, typ = "email")(autofocus, required, autocomplete := "email")
|
||||
),
|
||||
views.html.base.hcaptcha.tag(form),
|
||||
form3.action(form3.submit(trans.emailMeALink()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -106,12 +105,12 @@ object bits {
|
|||
)
|
||||
}
|
||||
|
||||
def magicLink(form: RecaptchaForm[_], fail: Boolean)(implicit ctx: Context) =
|
||||
def magicLink(form: HcaptchaForm[_], fail: Boolean)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "Log in by email",
|
||||
moreCss = cssTag("auth"),
|
||||
moreJs = views.html.base.recaptcha.script(form),
|
||||
csp = defaultCsp.withRecaptcha.some
|
||||
moreJs = views.html.base.hcaptcha.script(form),
|
||||
csp = defaultCsp.withHcaptcha.some
|
||||
) {
|
||||
main(cls := "auth auth-signup box box-pad")(
|
||||
h1(
|
||||
|
@ -119,13 +118,12 @@ object bits {
|
|||
"Log in by email"
|
||||
),
|
||||
p("We will send you an email containing a link to log you in."),
|
||||
postForm(id := form.formId, cls := "form3", action := routes.Auth.magicLinkApply)(
|
||||
postForm(cls := "form3", action := routes.Auth.magicLinkApply)(
|
||||
form3.group(form("email"), trans.email())(
|
||||
form3.input(_, typ = "email")(autofocus, autocomplete := "email")
|
||||
form3.input(_, typ = "email")(autofocus, required, autocomplete := "email")
|
||||
),
|
||||
form3.action(views.html.base.recaptcha.button(form) {
|
||||
form3.submit(trans.emailMeALink())
|
||||
})
|
||||
views.html.base.hcaptcha.tag(form),
|
||||
form3.action(form3.submit(trans.emailMeALink()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,25 +9,21 @@ import controllers.routes
|
|||
|
||||
object signup {
|
||||
|
||||
def apply(form: lila.security.RecaptchaForm[_])(implicit ctx: Context) =
|
||||
def apply(form: lila.security.HcaptchaForm[_])(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.signUp.txt(),
|
||||
moreJs = frag(
|
||||
jsModule("login"),
|
||||
embedJsUnsafeLoadThen("""loginSignup.signupStart()"""),
|
||||
views.html.base.recaptcha.script(form),
|
||||
fingerprintTag,
|
||||
embedJsUnsafeLoadThen("""
|
||||
lichess.loadModule('passwordComplexity').then(() =>
|
||||
passwordComplexity.addPasswordChangeListener('form3-password')
|
||||
)""")
|
||||
views.html.base.hcaptcha.script(form),
|
||||
fingerprintTag
|
||||
),
|
||||
moreCss = cssTag("auth"),
|
||||
csp = defaultCsp.withRecaptcha.some
|
||||
csp = defaultCsp.withHcaptcha.some
|
||||
) {
|
||||
main(cls := "auth auth-signup box box-pad")(
|
||||
h1(trans.signUp()),
|
||||
postForm(id := form.formId, cls := "form3", action := routes.Auth.signupPost)(
|
||||
postForm(id := "signup-form", cls := "form3", action := routes.Auth.signupPost)(
|
||||
auth.bits.formFields(form("username"), form("password"), form("email").some, register = true),
|
||||
input(id := "signup-fp-input", name := "fp", tpe := "hidden"),
|
||||
div(cls := "form-group text", dataIcon := "")(
|
||||
|
@ -41,9 +37,8 @@ object signup {
|
|||
)
|
||||
),
|
||||
agreement(form("agreement"), form.form.errors.exists(_.key startsWith "agreement.")),
|
||||
views.html.base.recaptcha.button(form) {
|
||||
button(cls := "submit button text big")(trans.signUp())
|
||||
}
|
||||
views.html.base.hcaptcha.tag(form),
|
||||
button(cls := "submit button text big")(trans.signUp())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package views.html
|
||||
package base
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.security.HcaptchaForm
|
||||
|
||||
object hcaptcha {
|
||||
|
||||
private val dataSitekey = attr("data-sitekey")
|
||||
|
||||
def script(re: HcaptchaForm[_])(implicit ctx: Context) =
|
||||
re.enabled option raw("""<script src="https://hcaptcha.com/1/api.js" async defer></script>""")
|
||||
|
||||
def tag(form: HcaptchaForm[_]) =
|
||||
div(cls := "h-captcha form-group", dataSitekey := form.config.key)
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package views.html
|
||||
package base
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.security.RecaptchaForm
|
||||
|
||||
object recaptcha {
|
||||
|
||||
private val callbackFunction = "recaptchaSubmit"
|
||||
|
||||
def script(re: RecaptchaForm[_])(implicit ctx: Context) =
|
||||
re.enabled option frag(
|
||||
raw(
|
||||
"""<script src="https://www.google.com/recaptcha/api.js" async defer></script>"""
|
||||
),
|
||||
embedJsUnsafe(
|
||||
s"""$callbackFunction=t=>document.getElementById('${re.formId}').submit()"""
|
||||
)
|
||||
)
|
||||
|
||||
def button(re: RecaptchaForm[_])(tag: Tag) =
|
||||
tag(
|
||||
cls := "g-recaptcha",
|
||||
attr("data-sitekey") := re.config.key,
|
||||
attr("data-callback") := callbackFunction
|
||||
)
|
||||
}
|
|
@ -236,7 +236,7 @@ security {
|
|||
enabled = false
|
||||
url = "http://ip2proxy.lichess.ovh:1929"
|
||||
}
|
||||
recaptcha = ${recaptcha}
|
||||
hcaptcha = ${hcaptcha}
|
||||
lame_name_check = true
|
||||
}
|
||||
oauth {
|
||||
|
@ -248,9 +248,9 @@ oauth {
|
|||
app = oauth_client
|
||||
}
|
||||
}
|
||||
recaptcha {
|
||||
endpoint = "https://www.google.com/recaptcha/api/siteverify"
|
||||
public_key = "6LcxLTUUAAAAAFwWrEgEehMJ6VXrtVOKIifTLGoW"
|
||||
hcaptcha {
|
||||
endpoint = "https://hcaptcha.com/siteverify"
|
||||
public_key = "f91a151d-73e5-4a95-9d4e-74bfa19bec9d"
|
||||
private_key = ""
|
||||
enabled = false
|
||||
}
|
||||
|
|
|
@ -46,10 +46,14 @@ case class ContentSecurityPolicy(
|
|||
|
||||
def withGoogleForm = copy(frameSrc = "https://docs.google.com" :: frameSrc)
|
||||
|
||||
def withRecaptcha =
|
||||
private val hCaptchaDomains = List("https://hcaptcha.com", "https://*.hcaptcha.com")
|
||||
|
||||
def withHcaptcha =
|
||||
copy(
|
||||
scriptSrc = "https://www.google.com" :: scriptSrc,
|
||||
frameSrc = "https://www.google.com" :: frameSrc
|
||||
scriptSrc = hCaptchaDomains ::: scriptSrc,
|
||||
frameSrc = hCaptchaDomains ::: frameSrc,
|
||||
styleSrc = hCaptchaDomains ::: styleSrc,
|
||||
connectSrc = hCaptchaDomains ::: connectSrc
|
||||
)
|
||||
|
||||
def withPeer = copy(connectSrc = "wss://0.peerjs.com" :: connectSrc)
|
||||
|
|
|
@ -315,9 +315,9 @@ object mon {
|
|||
}
|
||||
def usersAlikeTime(field: String) = timer("security.usersAlike.time").withTag("field", field)
|
||||
def usersAlikeFound(field: String) = histogram("security.usersAlike.found").withTag("field", field)
|
||||
object recaptcha {
|
||||
object hCaptcha {
|
||||
def hit(client: String, result: String) =
|
||||
counter("recaptcha.hit").withTags(Map("client" -> client, "result" -> result))
|
||||
counter("hcaptcha.hit").withTags(Map("client" -> client, "result" -> result))
|
||||
}
|
||||
}
|
||||
object tv {
|
||||
|
|
|
@ -36,10 +36,10 @@ final class Env(
|
|||
|
||||
private val config = appConfig.get[SecurityConfig]("security")(SecurityConfig.loader)
|
||||
|
||||
private def recaptchaPublicConfig = config.recaptcha.public
|
||||
private def hcaptchaPublicConfig = config.hcaptcha.public
|
||||
|
||||
def recaptcha[A](formId: String, form: play.api.data.Form[A]) =
|
||||
RecaptchaForm(form, formId, config.recaptcha.public)
|
||||
def hcaptcha[A](form: play.api.data.Form[A]) =
|
||||
HcaptchaForm(form, config.hcaptcha.public)
|
||||
|
||||
lazy val firewall = new Firewall(
|
||||
coll = db(config.collection.firewall),
|
||||
|
@ -48,9 +48,9 @@ final class Env(
|
|||
|
||||
lazy val flood = wire[Flood]
|
||||
|
||||
lazy val recaptcha: Recaptcha =
|
||||
if (config.recaptcha.enabled) wire[RecaptchaGoogle]
|
||||
else RecaptchaSkip
|
||||
lazy val hcaptcha: Hcaptcha =
|
||||
if (config.hcaptcha.enabled) wire[HcaptchaReal]
|
||||
else HcaptchaSkip
|
||||
|
||||
lazy val forms = wire[SecurityForm]
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@ import play.api.mvc.RequestHeader
|
|||
import lila.common.config._
|
||||
import lila.common.HTTPRequest
|
||||
|
||||
trait Recaptcha {
|
||||
trait Hcaptcha {
|
||||
|
||||
def verify(response: String, req: RequestHeader): Fu[Recaptcha.Result]
|
||||
def verify(response: String, req: RequestHeader): Fu[Hcaptcha.Result]
|
||||
|
||||
def verify()(implicit req: play.api.mvc.Request[_], formBinding: FormBinding): Fu[Recaptcha.Result] =
|
||||
verify(~Recaptcha.form.bindFromRequest().value.flatten, req)
|
||||
def verify()(implicit req: play.api.mvc.Request[_], formBinding: FormBinding): Fu[Hcaptcha.Result] =
|
||||
verify(~Hcaptcha.form.bindFromRequest().value.flatten, req)
|
||||
}
|
||||
|
||||
object Recaptcha {
|
||||
object Hcaptcha {
|
||||
|
||||
sealed abstract class Result(val ok: Boolean)
|
||||
object Result {
|
||||
|
@ -30,7 +30,7 @@ object Recaptcha {
|
|||
case object Fail extends Result(false)
|
||||
}
|
||||
|
||||
val field = "g-recaptcha-response" -> optional(nonEmptyText)
|
||||
val field = "h-captcha-response" -> optional(nonEmptyText)
|
||||
val form = Form(single(field))
|
||||
|
||||
private[security] case class Config(
|
||||
|
@ -39,30 +39,27 @@ object Recaptcha {
|
|||
@ConfigName("private_key") privateKey: Secret,
|
||||
enabled: Boolean
|
||||
) {
|
||||
def public = RecaptchaPublicConfig(publicKey, enabled)
|
||||
def public = HcaptchaPublicConfig(publicKey, enabled)
|
||||
}
|
||||
implicit private[security] val configLoader = AutoConfig.loader[Config]
|
||||
}
|
||||
|
||||
object RecaptchaSkip extends Recaptcha {
|
||||
object HcaptchaSkip extends Hcaptcha {
|
||||
|
||||
def verify(response: String, req: RequestHeader) = fuccess(Recaptcha.Result.Valid)
|
||||
def verify(response: String, req: RequestHeader) = fuccess(Hcaptcha.Result.Valid)
|
||||
|
||||
}
|
||||
|
||||
final class RecaptchaGoogle(
|
||||
final class HcaptchaReal(
|
||||
ws: StandaloneWSClient,
|
||||
netDomain: NetDomain,
|
||||
config: Recaptcha.Config
|
||||
config: Hcaptcha.Config
|
||||
)(implicit ec: scala.concurrent.ExecutionContext)
|
||||
extends Recaptcha {
|
||||
extends Hcaptcha {
|
||||
|
||||
import Recaptcha.Result
|
||||
import Hcaptcha.Result
|
||||
|
||||
private case class GoodResponse(
|
||||
success: Boolean,
|
||||
hostname: String
|
||||
)
|
||||
private case class GoodResponse(success: Boolean, hostname: String)
|
||||
implicit private val goodReader = Json.reads[GoodResponse]
|
||||
|
||||
private case class BadResponse(
|
||||
|
@ -80,34 +77,35 @@ final class RecaptchaGoogle(
|
|||
Map(
|
||||
"secret" -> config.privateKey.value,
|
||||
"response" -> response,
|
||||
"remoteip" -> HTTPRequest.ipAddress(req).value
|
||||
"remoteip" -> HTTPRequest.ipAddress(req).value,
|
||||
"sitekey" -> config.publicKey
|
||||
)
|
||||
) map {
|
||||
case res if res.status == 200 =>
|
||||
res.body[JsValue].validate[GoodResponse] match {
|
||||
case JsSuccess(res, _) =>
|
||||
lila.mon.security.recaptcha.hit(client, "success").increment()
|
||||
lila.mon.security.hCaptcha.hit(client, "success").increment()
|
||||
if (res.success && res.hostname == netDomain.value) Result.Valid
|
||||
else Result.Fail
|
||||
case JsError(err) =>
|
||||
res.body[JsValue].validate[BadResponse].asOpt match {
|
||||
case Some(err) if err.missingInput =>
|
||||
logger.info(s"recaptcha missing ${HTTPRequest printClient req}")
|
||||
lila.mon.security.recaptcha.hit(client, "missing").increment()
|
||||
logger.info(s"hcaptcha missing ${HTTPRequest printClient req}")
|
||||
lila.mon.security.hCaptcha.hit(client, "missing").increment()
|
||||
if (HTTPRequest.apiVersion(req).isDefined) Result.Pass else Result.Fail
|
||||
case Some(err) =>
|
||||
lila.mon.security.recaptcha.hit(client, err.toString).increment()
|
||||
lila.mon.security.hCaptcha.hit(client, err.toString).increment()
|
||||
Result.Fail
|
||||
case _ =>
|
||||
lila.mon.security.recaptcha.hit(client, "error").increment()
|
||||
logger.info(s"recaptcha $err ${res.body}")
|
||||
lila.mon.security.hCaptcha.hit(client, "error").increment()
|
||||
logger.info(s"hcaptcha $err ${res.body}")
|
||||
Result.Fail
|
||||
}
|
||||
}
|
||||
case res =>
|
||||
lila.mon.security.recaptcha.hit(client, res.status.toString).increment()
|
||||
logger.info(s"recaptcha ${res.status} ${res.body}")
|
||||
lila.mon.security.hCaptcha.hit(client, res.status.toString).increment()
|
||||
logger.info(s"hcaptcha ${res.status} ${res.body}")
|
||||
Result.Fail
|
||||
}
|
||||
}
|
||||
}.thenPp
|
||||
}
|
|
@ -21,7 +21,7 @@ final private class SecurityConfig(
|
|||
@ConfigName("disposable_email") val disposableEmail: DisposableEmail,
|
||||
@ConfigName("dns_api") val dnsApi: DnsApi,
|
||||
@ConfigName("check_mail_api") val checkMail: CheckMail,
|
||||
val recaptcha: Recaptcha.Config,
|
||||
val hcaptcha: Hcaptcha.Config,
|
||||
val mailer: Mailer.Config,
|
||||
@ConfigName("ip2proxy") val ip2Proxy: Ip2Proxy,
|
||||
@ConfigName("lame_name_check") val lameNameCheck: LameNameCheck
|
||||
|
|
|
@ -14,7 +14,7 @@ final class SecurityForm(
|
|||
authenticator: lila.user.Authenticator,
|
||||
emailValidator: EmailAddressValidator,
|
||||
lameNameCheck: LameNameCheck,
|
||||
recaptchaPublicConfig: RecaptchaPublicConfig
|
||||
hcaptchaPublicConfig: HcaptchaPublicConfig
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import SecurityForm._
|
||||
|
@ -77,7 +77,7 @@ final class SecurityForm(
|
|||
|
||||
val emailField = withAcceptableDns(acceptableUniqueEmail(none))
|
||||
|
||||
val website = RecaptchaForm(
|
||||
val website = HcaptchaForm(
|
||||
Form(
|
||||
mapping(
|
||||
"username" -> username,
|
||||
|
@ -87,8 +87,7 @@ final class SecurityForm(
|
|||
"fp" -> optional(nonEmptyText)
|
||||
)(SignupData.apply)(_ => None)
|
||||
),
|
||||
"signup-form",
|
||||
recaptchaPublicConfig
|
||||
hcaptchaPublicConfig
|
||||
)
|
||||
|
||||
val mobile = Form(
|
||||
|
@ -100,14 +99,13 @@ final class SecurityForm(
|
|||
)
|
||||
}
|
||||
|
||||
val passwordReset = RecaptchaForm(
|
||||
val passwordReset = HcaptchaForm(
|
||||
Form(
|
||||
mapping(
|
||||
"email" -> sendableEmail // allow unacceptable emails for BC
|
||||
)(PasswordReset.apply)(_ => None)
|
||||
),
|
||||
"password-reset-form",
|
||||
recaptchaPublicConfig
|
||||
hcaptchaPublicConfig
|
||||
)
|
||||
|
||||
val newPassword = Form(
|
||||
|
@ -130,14 +128,13 @@ final class SecurityForm(
|
|||
)
|
||||
)
|
||||
|
||||
val magicLink = RecaptchaForm(
|
||||
val magicLink = HcaptchaForm(
|
||||
Form(
|
||||
mapping(
|
||||
"email" -> sendableEmail // allow unacceptable emails for BC
|
||||
)(MagicLink.apply)(_ => None)
|
||||
),
|
||||
"magic-link-form",
|
||||
recaptchaPublicConfig
|
||||
hcaptchaPublicConfig
|
||||
)
|
||||
|
||||
def changeEmail(u: User, old: Option[EmailAddress]) =
|
||||
|
@ -207,15 +204,14 @@ final class SecurityForm(
|
|||
|
||||
def toggleKid = passwordProtected _
|
||||
|
||||
val reopen = RecaptchaForm(
|
||||
val reopen = HcaptchaForm(
|
||||
Form(
|
||||
mapping(
|
||||
"username" -> LilaForm.cleanNonEmptyText,
|
||||
"email" -> sendableEmail // allow unacceptable emails for BC
|
||||
)(Reopen.apply)(_ => None)
|
||||
),
|
||||
"reopen-form",
|
||||
recaptchaPublicConfig
|
||||
hcaptchaPublicConfig
|
||||
)
|
||||
|
||||
private def passwordMapping(candidate: User.LoginCandidate) =
|
||||
|
|
|
@ -18,7 +18,7 @@ final class Signup(
|
|||
forms: SecurityForm,
|
||||
emailAddressValidator: EmailAddressValidator,
|
||||
emailConfirm: EmailConfirm,
|
||||
recaptcha: Recaptcha,
|
||||
hcaptcha: Hcaptcha,
|
||||
authenticator: lila.user.Authenticator,
|
||||
userRepo: lila.user.UserRepo,
|
||||
slack: lila.irc.SlackApi,
|
||||
|
@ -59,16 +59,16 @@ final class Signup(
|
|||
def website(
|
||||
blind: Boolean
|
||||
)(implicit req: Request[_], lang: Lang, formBinding: FormBinding): Fu[Signup.Result] =
|
||||
recaptcha.verify().flatMap {
|
||||
case Recaptcha.Result.Fail => fuccess(Signup.MissingCaptcha)
|
||||
case Recaptcha.Result.Pass if !blind => fuccess(Signup.MissingCaptcha)
|
||||
case recaptchaResult =>
|
||||
hcaptcha.verify().flatMap {
|
||||
case Hcaptcha.Result.Fail => fuccess(Signup.MissingCaptcha)
|
||||
case Hcaptcha.Result.Pass if !blind => fuccess(Signup.MissingCaptcha)
|
||||
case hcaptchaResult =>
|
||||
forms.signup.website.form
|
||||
.bindFromRequest()
|
||||
.fold[Fu[Signup.Result]](
|
||||
err => fuccess(Signup.Bad(err tap signupErrLog)),
|
||||
data =>
|
||||
signupRateLimit(data.username, if (recaptchaResult == Recaptcha.Result.Valid) 1 else 3) {
|
||||
signupRateLimit(data.username, if (hcaptchaResult == Hcaptcha.Result.Valid) 1 else 3) {
|
||||
MustConfirmEmail(data.fingerPrint) flatMap { mustConfirm =>
|
||||
lila.mon.user.register.count(none)
|
||||
lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment()
|
||||
|
|
|
@ -36,12 +36,12 @@ case class LocatedSession(session: UserSession, location: Option[Location])
|
|||
|
||||
case class IpAndFp(ip: IpAddress, fp: Option[String], user: User.ID)
|
||||
|
||||
case class RecaptchaPublicConfig(key: String, enabled: Boolean)
|
||||
case class HcaptchaPublicConfig(key: String, enabled: Boolean)
|
||||
|
||||
case class RecaptchaForm[A](form: Form[A], formId: String, config: RecaptchaPublicConfig) {
|
||||
case class HcaptchaForm[A](form: Form[A], config: HcaptchaPublicConfig) {
|
||||
def enabled = config.enabled
|
||||
def apply(key: String) = form(key)
|
||||
def withForm[B](form: Form[B]) = RecaptchaForm(form, formId, config)
|
||||
def withForm[B](form: Form[B]) = HcaptchaForm(form, config)
|
||||
}
|
||||
|
||||
case class LameNameCheck(value: Boolean) extends AnyVal
|
||||
|
|
|
@ -60,12 +60,23 @@ export function signupStart() {
|
|||
xhr.json(xhr.url('/player/autocomplete', { term: name, exists: 1 })).then(res => $exists.toggle(res));
|
||||
}, 300);
|
||||
|
||||
$form.on('submit', () =>
|
||||
$form.find('button.submit').prop('disabled', true).removeAttr('data-icon').addClass('frameless').html(spinnerHtml)
|
||||
);
|
||||
$form.on('submit', () => {
|
||||
if ($form.find('[name="h-captcha-response"]').val())
|
||||
$form
|
||||
.find('button.submit')
|
||||
.prop('disabled', true)
|
||||
.removeAttr('data-icon')
|
||||
.addClass('frameless')
|
||||
.html(spinnerHtml);
|
||||
else return false;
|
||||
});
|
||||
|
||||
window.signupSubmit = () => {
|
||||
const form = document.getElementById('signup-form') as HTMLFormElement;
|
||||
if (form.reportValidity()) form.submit();
|
||||
};
|
||||
|
||||
lichess
|
||||
.loadModule('passwordComplexity')
|
||||
.then(() => window['passwordComplexity'].addPasswordChangeListener('form3-password'));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue