diff --git a/COPYING.md b/COPYING.md
index ae60b01df9..a4dc27d472 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -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
diff --git a/app/controllers/Account.scala b/app/controllers/Account.scala
index 70c31e9e72..c92562a629 100644
--- a/app/controllers/Account.scala
+++ b/app/controllers/Account.scala
@@ -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
}
}
diff --git a/app/controllers/Auth.scala b/app/controllers/Auth.scala
index 169d8676b5..a98ca6bd16 100644
--- a/app/controllers/Auth.scala
+++ b/app/controllers/Auth.scala
@@ -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
}
}
diff --git a/app/views/account/reopen.scala b/app/views/account/reopen.scala
index 934096c19f..ed64fa06ee 100644
--- a/app/views/account/reopen.scala
+++ b/app/views/account/reopen.scala
@@ -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()))
)
)
}
diff --git a/app/views/auth/bits.scala b/app/views/auth/bits.scala
index faa28a78a7..46a28f3236 100644
--- a/app/views/auth/bits.scala
+++ b/app/views/auth/bits.scala
@@ -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()))
)
)
}
diff --git a/app/views/auth/signup.scala b/app/views/auth/signup.scala
index 79cda0ac03..55d7d3bb3f 100644
--- a/app/views/auth/signup.scala
+++ b/app/views/auth/signup.scala
@@ -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())
)
)
}
diff --git a/app/views/base/hcaptcha.scala b/app/views/base/hcaptcha.scala
new file mode 100644
index 0000000000..96e62f0dbd
--- /dev/null
+++ b/app/views/base/hcaptcha.scala
@@ -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("""""")
+
+ def tag(form: HcaptchaForm[_]) =
+ div(cls := "h-captcha form-group", dataSitekey := form.config.key)
+}
diff --git a/app/views/base/recaptcha.scala b/app/views/base/recaptcha.scala
deleted file mode 100644
index a557799980..0000000000
--- a/app/views/base/recaptcha.scala
+++ /dev/null
@@ -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(
- """"""
- ),
- 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
- )
-}
diff --git a/conf/base.conf b/conf/base.conf
index bb87815a90..6e69e0a27b 100644
--- a/conf/base.conf
+++ b/conf/base.conf
@@ -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
}
diff --git a/modules/common/src/main/ContentSecurityPolicy.scala b/modules/common/src/main/ContentSecurityPolicy.scala
index 7324f5b291..7f69f34d33 100644
--- a/modules/common/src/main/ContentSecurityPolicy.scala
+++ b/modules/common/src/main/ContentSecurityPolicy.scala
@@ -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)
diff --git a/modules/common/src/main/mon.scala b/modules/common/src/main/mon.scala
index 32e1dfba46..765c3922a0 100644
--- a/modules/common/src/main/mon.scala
+++ b/modules/common/src/main/mon.scala
@@ -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 {
diff --git a/modules/security/src/main/Env.scala b/modules/security/src/main/Env.scala
index b911fc1b1d..0a49c06761 100644
--- a/modules/security/src/main/Env.scala
+++ b/modules/security/src/main/Env.scala
@@ -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]
diff --git a/modules/security/src/main/Recaptcha.scala b/modules/security/src/main/Hcaptcha.scala
similarity index 64%
rename from modules/security/src/main/Recaptcha.scala
rename to modules/security/src/main/Hcaptcha.scala
index 318a48c02c..b42f4f0702 100644
--- a/modules/security/src/main/Recaptcha.scala
+++ b/modules/security/src/main/Hcaptcha.scala
@@ -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
}
diff --git a/modules/security/src/main/SecurityConfig.scala b/modules/security/src/main/SecurityConfig.scala
index 21f5a40500..e27531bcdb 100644
--- a/modules/security/src/main/SecurityConfig.scala
+++ b/modules/security/src/main/SecurityConfig.scala
@@ -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
diff --git a/modules/security/src/main/SecurityForm.scala b/modules/security/src/main/SecurityForm.scala
index 846a8ba17e..0a3dc21f55 100644
--- a/modules/security/src/main/SecurityForm.scala
+++ b/modules/security/src/main/SecurityForm.scala
@@ -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) =
diff --git a/modules/security/src/main/Signup.scala b/modules/security/src/main/Signup.scala
index 0485c0c634..cd894d17dc 100644
--- a/modules/security/src/main/Signup.scala
+++ b/modules/security/src/main/Signup.scala
@@ -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()
diff --git a/modules/security/src/main/model.scala b/modules/security/src/main/model.scala
index e427f0e47f..17b866afd1 100644
--- a/modules/security/src/main/model.scala
+++ b/modules/security/src/main/model.scala
@@ -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
diff --git a/ui/site/src/login.ts b/ui/site/src/login.ts
index fdb5c14a27..d412bd69e8 100644
--- a/ui/site/src/login.ts
+++ b/ui/site/src/login.ts
@@ -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'));
}