implement registration chess captcha
This commit is contained in:
parent
d2d60f1ff7
commit
ac552594a2
|
@ -12,6 +12,7 @@ import play.api.mvc.Results._
|
|||
object Auth extends LilaController {
|
||||
|
||||
def userRepo = env.user.userRepo
|
||||
def forms = env.security.forms
|
||||
|
||||
def login = Open { implicit ctx ⇒
|
||||
Ok(html.auth.login(loginForm))
|
||||
|
@ -32,16 +33,20 @@ object Auth extends LilaController {
|
|||
}
|
||||
|
||||
def signup = Open { implicit ctx ⇒
|
||||
Ok(html.auth.signup(signupForm))
|
||||
val (form, captcha) = forms.signupWithCaptcha
|
||||
Ok(html.auth.signup(form, captcha))
|
||||
}
|
||||
|
||||
def signupPost = OpenBody { implicit ctx ⇒
|
||||
implicit val req = ctx.body
|
||||
signupForm.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.auth.signup(err)),
|
||||
userOption ⇒ gotoSignupSucceeded(
|
||||
userOption.err("authenticate error").username
|
||||
)
|
||||
forms.signup.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.auth.signup(err, forms.captchaCreate)),
|
||||
data ⇒ {
|
||||
val user = userRepo.create(data.username, data.password).unsafePerformIO
|
||||
gotoSignupSucceeded(
|
||||
user.err("register error").username
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ object User extends LilaController {
|
|||
me ⇒
|
||||
IORedirect {
|
||||
userRepo disable me map { _ ⇒
|
||||
env.securityStore deleteUsername me.username
|
||||
env.security.store deleteUsername me.username
|
||||
routes.User show me.username
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,12 @@ final class CoreEnv private (application: Application, val settings: Settings) {
|
|||
settings = settings,
|
||||
gameRepo = game.gameRepo)
|
||||
|
||||
lazy val security = new lila.security.SecurityEnv(
|
||||
settings = settings,
|
||||
captcha = site.captcha,
|
||||
mongodb = mongodb.apply _,
|
||||
userRepo = user.userRepo)
|
||||
|
||||
lazy val metaHub = new lila.socket.MetaHub(
|
||||
List(site.hub, lobby.hub, round.hubMaster))
|
||||
|
||||
|
@ -105,13 +111,6 @@ final class CoreEnv private (application: Application, val settings: Settings) {
|
|||
mongodb = mongodb.connection,
|
||||
settings = settings)
|
||||
|
||||
lazy val securityStore = new security.Store(
|
||||
collection = mongodb(MongoCollectionSecurity))
|
||||
|
||||
lazy val firewall = new security.Firewall(
|
||||
collection = mongodb(MongoCollectionFirewall),
|
||||
cacheTtl = FirewallCacheTtl)
|
||||
|
||||
lazy val titivate = new core.Titivate(
|
||||
gameRepo = game.gameRepo,
|
||||
finisher = round.finisher)
|
||||
|
|
|
@ -21,7 +21,7 @@ object Global extends GlobalSettings {
|
|||
|
||||
override def onRouteRequest(req: RequestHeader): Option[Handler] = {
|
||||
env.monitor.rpsProvider.countRequest()
|
||||
env.firewall.requestHandler(req) orElse
|
||||
env.security.firewall.requestHandler(req) orElse
|
||||
env.i18n.requestHandler(req) orElse
|
||||
super.onRouteRequest(req)
|
||||
}
|
||||
|
|
|
@ -26,19 +26,6 @@ trait AuthImpl {
|
|||
.verifying("Invalid username or password", _.isDefined)
|
||||
)
|
||||
|
||||
val signupForm = Form(mapping(
|
||||
"username" -> of[String].verifying(
|
||||
Constraints minLength 2,
|
||||
Constraints maxLength 20,
|
||||
Constraints.pattern(
|
||||
regex = """^[\w-]+$""".r,
|
||||
error = "Invalid username. Please use only letters, numbers and dash")
|
||||
),
|
||||
"password" -> text(minLength = 4)
|
||||
)(signupUser)(_ ⇒ None)
|
||||
.verifying("This user already exists", _.isDefined)
|
||||
)
|
||||
|
||||
def env: CoreEnv
|
||||
|
||||
def logoutSucceeded(req: RequestHeader): PlainResult =
|
||||
|
@ -49,7 +36,7 @@ trait AuthImpl {
|
|||
|
||||
def saveAuthentication(username: String)(implicit req: RequestHeader): String =
|
||||
(OrnicarRandom nextAsciiString 12) ~ { sessionId ⇒
|
||||
env.securityStore.save(sessionId, username, req)
|
||||
env.security.store.save(sessionId, username, req)
|
||||
}
|
||||
|
||||
def gotoLoginSucceeded[A](username: String)(implicit req: RequestHeader) = {
|
||||
|
@ -63,7 +50,7 @@ trait AuthImpl {
|
|||
}
|
||||
|
||||
def gotoLogoutSucceeded(implicit req: RequestHeader) = {
|
||||
req.session.get("sessionId") foreach env.securityStore.delete
|
||||
req.session.get("sessionId") foreach env.security.store.delete
|
||||
logoutSucceeded(req).withNewSession
|
||||
}
|
||||
|
||||
|
@ -79,12 +66,9 @@ trait AuthImpl {
|
|||
def authenticateUser(username: String, password: String): Option[User] =
|
||||
env.user.userRepo.authenticate(username, password).unsafePerformIO
|
||||
|
||||
def signupUser(username: String, password: String): Option[User] =
|
||||
env.user.userRepo.create(username, password).unsafePerformIO
|
||||
|
||||
def restoreUser(req: RequestHeader): Option[User] = for {
|
||||
sessionId ← req.session.get("sessionId")
|
||||
username ← env.securityStore.getUsername(sessionId)
|
||||
username ← env.security.store.getUsername(sessionId)
|
||||
user ← (env.user.userRepo byId username).unsafePerformIO
|
||||
} yield user
|
||||
}
|
||||
|
|
51
app/security/DataForm.scala
Normal file
51
app/security/DataForm.scala
Normal file
|
@ -0,0 +1,51 @@
|
|||
package lila
|
||||
package security
|
||||
|
||||
import user.{ User, UserRepo }
|
||||
import site.Captcha
|
||||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import play.api.data.validation.Constraints
|
||||
|
||||
final class DataForm(
|
||||
userRepo: UserRepo,
|
||||
captcher: Captcha) {
|
||||
|
||||
import DataForm._
|
||||
|
||||
val signup = Form(mapping(
|
||||
"username" -> nonEmptyText.verifying(
|
||||
Constraints minLength 2,
|
||||
Constraints maxLength 20,
|
||||
Constraints.pattern(
|
||||
regex = """^[\w-]+$""".r,
|
||||
error = "Invalid username. Please use only letters, numbers and dash")
|
||||
),
|
||||
"password" -> text(minLength = 4),
|
||||
"gameId" -> nonEmptyText,
|
||||
"move" -> nonEmptyText
|
||||
)(SignupData.apply)(_ ⇒ None)
|
||||
.verifying("This user already exists", d ⇒ !userExists(d))
|
||||
.verifying(
|
||||
"Not a checkmate",
|
||||
data ⇒ captcher get data.gameId valid data.move.trim.toLowerCase
|
||||
)
|
||||
)
|
||||
|
||||
def signupWithCaptcha = signup -> captchaCreate
|
||||
|
||||
def captchaCreate: Captcha.Challenge = captcher.create
|
||||
|
||||
private def userExists(data: SignupData) =
|
||||
userRepo.exists(data.username).unsafePerformIO
|
||||
}
|
||||
|
||||
object DataForm {
|
||||
|
||||
case class SignupData(
|
||||
username: String,
|
||||
password: String,
|
||||
gameId: String,
|
||||
move: String)
|
||||
}
|
28
app/security/SecurityEnv.scala
Normal file
28
app/security/SecurityEnv.scala
Normal file
|
@ -0,0 +1,28 @@
|
|||
package lila
|
||||
package security
|
||||
|
||||
import user.{ User, UserRepo }
|
||||
import core.Settings
|
||||
import site.Captcha
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
|
||||
final class SecurityEnv(
|
||||
settings: Settings,
|
||||
captcha: Captcha,
|
||||
mongodb: String ⇒ MongoCollection,
|
||||
userRepo: UserRepo) {
|
||||
|
||||
import settings._
|
||||
|
||||
lazy val store = new security.Store(
|
||||
collection = mongodb(MongoCollectionSecurity))
|
||||
|
||||
lazy val firewall = new security.Firewall(
|
||||
collection = mongodb(MongoCollectionFirewall),
|
||||
cacheTtl = FirewallCacheTtl)
|
||||
|
||||
lazy val forms = new DataForm(
|
||||
userRepo = userRepo,
|
||||
captcher = captcha)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
@(form: Form[_])(implicit ctx: Context)
|
||||
@(form: Form[_], captcha: lila.site.Captcha.Challenge)(implicit ctx: Context)
|
||||
|
||||
@auth.layout(
|
||||
title = trans.signUp.str()) {
|
||||
|
@ -17,11 +17,12 @@ title = trans.signUp.str()) {
|
|||
</em>
|
||||
</p>
|
||||
<form action="@routes.Auth.signupPost" method="POST">
|
||||
@form.globalError.map { error =>
|
||||
<p class="error">@error.message</p>
|
||||
}
|
||||
<ul>
|
||||
@auth.formFields(form("username"), form("password"))
|
||||
<li>
|
||||
@base.captcha(form("move"), form("gameId"), captcha)
|
||||
</li>
|
||||
@errMsg(form)
|
||||
<li>
|
||||
<input type="submit" value="@trans.signUp()" class="submit">
|
||||
<span class="alternative">@trans.haveAnAccount() <a href="@routes.Auth.login">@trans.signIn()</a></span>
|
||||
|
|
|
@ -9,12 +9,10 @@
|
|||
</div>
|
||||
<div class="checkmateSection">
|
||||
<label>
|
||||
<div>
|
||||
<strong>@captcha.color.toString.capitalize plays, mate in one!</strong><br />
|
||||
This is a chess CAPTCHA.<br />
|
||||
Click on the board to make your move,<br />
|
||||
and prove you are human.<br /><br />
|
||||
</div>
|
||||
<strong>@captcha.color.toString.capitalize plays, mate in one!</strong><br />
|
||||
This is a chess CAPTCHA.<br />
|
||||
Click on the board to make your move,<br />
|
||||
and prove you are human.<br /><br />
|
||||
<input type="text" name="@move.name" id="@move.id" />
|
||||
@errMsg(move)
|
||||
</label>
|
|
@ -15,7 +15,7 @@ title = "New forum topic") {
|
|||
@errMsg(form("name"))
|
||||
</label>
|
||||
@forum.post.formFields(form("post")("text"), form("post")("author"))
|
||||
@forum.post.captcha(form("post")("move"), form("post")("gameId"), captcha)
|
||||
@base.captcha(form("post")("move"), form("post")("gameId"), captcha)
|
||||
@errMsg(form("post"))
|
||||
<button class="submit button" type="submit">Create the topic</button>
|
||||
<a href="@routes.ForumCateg.show(categ.slug)" style="margin-left:20px">Cancel</a>
|
||||
|
|
|
@ -36,7 +36,7 @@ title = topic.name) {
|
|||
<h2 class="postNewTitle" id="reply">Reply to this topic</h2>
|
||||
<form action="@routes.ForumPost.create(categ.slug, topic.slug, posts.currentPage)#reply" method="POST" novalidate>
|
||||
@forum.post.formFields(form("text"), form("author"))
|
||||
@forum.post.captcha(form("move"), form("gameId"), captcha)
|
||||
@base.captcha(form("move"), form("gameId"), captcha)
|
||||
@errMsg(form)
|
||||
<input type="submit" class="submit button" value="Reply" />
|
||||
<a href="@routes.ForumCateg.show(categ.slug)" style="margin-left:20px">Cancel</a>
|
||||
|
|
|
@ -16,7 +16,7 @@ object Main {
|
|||
|
||||
lazy val env = CoreEnv(app)
|
||||
|
||||
lazy val users = Users(env.user.userRepo, env.securityStore)
|
||||
lazy val users = Users(env.user.userRepo, env.security.store)
|
||||
lazy val games = Games(env)
|
||||
lazy val titivate = env.titivate
|
||||
lazy val forum = Forum(env.forum)
|
||||
|
|
|
@ -28,7 +28,7 @@ var lichess = {
|
|||
onProduction: /.+\.lichess\.org/.test(document.domain),
|
||||
socketUrl: document.domain + ":9000"
|
||||
};
|
||||
lichess.socketDefaults.options.debug = !lichess.onProduction;
|
||||
//lichess.socketDefaults.options.debug = !lichess.onProduction;
|
||||
|
||||
$(function() {
|
||||
|
||||
|
|
|
@ -1,15 +1,4 @@
|
|||
$(function() {
|
||||
$('.checkmateCaptcha').each(function() {
|
||||
var $captcha = $(this);
|
||||
var $input = $captcha.find('input');
|
||||
var i1, i2;
|
||||
$captcha.find('div.lmcs').click(function() {
|
||||
var key = $(this).data('key');
|
||||
i1 = $input.val();
|
||||
i2 = i1.length > 3 ? key : i1 + " " + key;
|
||||
$input.val(i2);
|
||||
});
|
||||
});
|
||||
$("#lichess_forum a.delete").unbind("click").click(function() {
|
||||
if (confirm("Delete?")) {
|
||||
var $this = $(this)
|
||||
|
|
|
@ -72,6 +72,17 @@ $(function() {
|
|||
});
|
||||
}
|
||||
parseFen();
|
||||
|
||||
$('body').on('lichess.content_loaded', parseFen);
|
||||
|
||||
$('div.checkmateCaptcha').each(function() {
|
||||
var $captcha = $(this);
|
||||
var $input = $captcha.find('input');
|
||||
var i1, i2;
|
||||
$captcha.find('div.lmcs').click(function() {
|
||||
var key = $(this).data('key');
|
||||
i1 = $input.val();
|
||||
i2 = i1.length > 3 ? key : i1 + " " + key;
|
||||
$input.val(i2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -544,3 +544,23 @@ div.game_share_top a.game_permalink {
|
|||
display: block;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
div.checkmateFen {
|
||||
float: left;
|
||||
width: 220px;
|
||||
margin-left: 110px;
|
||||
cursor: pointer;
|
||||
}
|
||||
div.checkmateFen div.lmcs:hover {
|
||||
background: #ddddff;
|
||||
}
|
||||
div.checkmateSection {
|
||||
float: left;
|
||||
}
|
||||
div.checkmateCaptcha input {
|
||||
padding: 0.5em;
|
||||
border: 1px solid #D4D4D4;
|
||||
background: #dfdfff;
|
||||
font-weight: bold;
|
||||
width: 5em;
|
||||
}
|
||||
|
|
|
@ -258,25 +258,6 @@ div.bar div.pagination {
|
|||
float: right;
|
||||
}
|
||||
|
||||
div.checkmateFen {
|
||||
float: left;
|
||||
width: 220px;
|
||||
margin-left: 110px;
|
||||
cursor: pointer;
|
||||
}
|
||||
div.checkmateFen div.lmcs:hover {
|
||||
background: #ddddff;
|
||||
}
|
||||
div.checkmateSection {
|
||||
float: left;
|
||||
}
|
||||
div.checkmateCaptcha input {
|
||||
padding: 0.5em;
|
||||
border: 1px solid #D4D4D4;
|
||||
background: #dfdfff;
|
||||
font-weight: bold;
|
||||
width: 5em;
|
||||
}
|
||||
#lichess_forum div.checkmateCaptcha p.error {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
@ -52,3 +52,14 @@ form .error {
|
|||
margin-left: 160px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.checkmateCaptcha {
|
||||
margin-top: 2em;
|
||||
}
|
||||
div.checkmateCaptcha div.checkmateFen {
|
||||
margin-left: 0;
|
||||
}
|
||||
div.checkmateCaptcha label {
|
||||
width: 250px;
|
||||
text-align: left;
|
||||
}
|
||||
|
|
1
todo
1
todo
|
@ -30,6 +30,7 @@ star people and games (and forum threads?)
|
|||
autoclose top menus
|
||||
strings and captchas http://fr.lichess.org/forum/off-topic-discussion/lichess-asks-lichess-answers?page=11#109
|
||||
tournaments http://www.chess.com/tournaments/help.html
|
||||
forum last post link broken http://fr.lichess.org/forum/off-topic-discussion/lichess-asks-lichess-answers?page=12#111
|
||||
|
||||
new translations:
|
||||
-rematchOfferCanceled=Rematch offer canceled
|
||||
|
|
Loading…
Reference in a new issue