rate limit hook form and post requests to prevent playban flood
This commit is contained in:
parent
9345227579
commit
8885265ba6
|
@ -5,6 +5,7 @@ import play.api.i18n.Messages.Implicits._
|
|||
import play.api.libs.json.Json
|
||||
import play.api.mvc.{ Result, Results, Call, RequestHeader, Accepting }
|
||||
import play.api.Play.current
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.api.{ Context, BodyContext }
|
||||
import lila.app._
|
||||
|
@ -18,6 +19,9 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
private def env = Env.setup
|
||||
|
||||
private val FormRateLimit = new lila.security.RateLimitByKey(1 second)
|
||||
private val PostRateLimit = new lila.security.RateLimitByKey(1 second)
|
||||
|
||||
def aiForm = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) {
|
||||
env.forms aiFilled get("fen") map { form =>
|
||||
|
@ -88,8 +92,10 @@ object Setup extends LilaController with TheftPrevention {
|
|||
}
|
||||
|
||||
def hookForm = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) NoPlaybanOrCurrent {
|
||||
env.forms.hookFilled(timeModeString = get("time")) map { html.setup.hook(_) }
|
||||
if (HTTPRequest isXhr ctx.req) FormRateLimit(ctx.req.remoteAddress) {
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hookFilled(timeModeString = get("time")) map { html.setup.hook(_) }
|
||||
}
|
||||
}
|
||||
else fuccess {
|
||||
Redirect(routes.Lobby.home + "#hook")
|
||||
|
@ -114,31 +120,35 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
def hook(uid: String) = OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hook(ctx).bindFromRequest.fold(
|
||||
err => negotiate(
|
||||
html = BadRequest(err.errorsAsJson.toString).fuccess,
|
||||
api = _ => BadRequest(err.errorsAsJson).fuccess),
|
||||
preConfig => (ctx.userId ?? Env.relation.api.blocking) zip
|
||||
mobileHookAllowAnon(preConfig) flatMap {
|
||||
case (blocking, config) =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
}
|
||||
)
|
||||
PostRateLimit(req.remoteAddress) {
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hook(ctx).bindFromRequest.fold(
|
||||
err => negotiate(
|
||||
html = BadRequest(err.errorsAsJson.toString).fuccess,
|
||||
api = _ => BadRequest(err.errorsAsJson).fuccess),
|
||||
preConfig => (ctx.userId ?? Env.relation.api.blocking) zip
|
||||
mobileHookAllowAnon(preConfig) flatMap {
|
||||
case (blocking, config) =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def like(uid: String, gameId: String) = Open { implicit ctx =>
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hookConfig flatMap { config =>
|
||||
GameRepo game gameId map {
|
||||
_.fold(config)(config.updateFrom)
|
||||
} flatMap { config =>
|
||||
(ctx.userId ?? Env.relation.api.blocking) flatMap { blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid ctx.req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
PostRateLimit(ctx.req.remoteAddress) {
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hookConfig flatMap { config =>
|
||||
GameRepo game gameId map {
|
||||
_.fold(config)(config.updateFrom)
|
||||
} flatMap { config =>
|
||||
(ctx.userId ?? Env.relation.api.blocking) flatMap { blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid ctx.req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,41 @@
|
|||
package lila.security
|
||||
|
||||
import scala.concurrent.duration.Duration
|
||||
import lila.memo.ExpireSetMemo
|
||||
import scala.concurrent.duration.Duration
|
||||
import ornicar.scalalib.Zero
|
||||
|
||||
/** very simple side effect throttler
|
||||
that allows one call per duration,
|
||||
and discards the rest */
|
||||
/**
|
||||
* very simple side effect throttler
|
||||
* that allows one call per duration,
|
||||
* and discards the rest
|
||||
*/
|
||||
final class RateLimitGlobal(duration: Duration) {
|
||||
|
||||
private val durationMillis = duration.toMillis
|
||||
|
||||
private var lastHit: Long = nowMillis - durationMillis - 10
|
||||
|
||||
def apply(op: => Unit) {
|
||||
if (nowMillis > lastHit + durationMillis) {
|
||||
def apply[A: Zero](op: => A): A = {
|
||||
(nowMillis > lastHit + durationMillis) ?? {
|
||||
lastHit = nowMillis
|
||||
op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* very simple side effect throttler
|
||||
* that allows one call per duration and per key,
|
||||
* and discards the rest
|
||||
*/
|
||||
final class RateLimitByKey(duration: Duration) {
|
||||
|
||||
private val storage = new ExpireSetMemo(ttl = duration)
|
||||
|
||||
def apply[A: Zero](key: String)(op: => A): A = {
|
||||
(!storage.get(key)) ?? {
|
||||
storage put key
|
||||
op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue