remove implicit rate limiter default to ensure 429 results
parent
51a8373ad3
commit
1a137617bb
|
@ -143,7 +143,7 @@ final class Account(
|
|||
Redirect(routes.Account.passwd).flashSuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
private def emailForm(user: UserModel) =
|
||||
|
@ -188,10 +188,10 @@ final class Account(
|
|||
Redirect(routes.Account.email).flashSuccess {
|
||||
lila.i18n.I18nKeys.checkYourEmail.txt()
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def emailConfirm(token: String) =
|
||||
|
@ -259,7 +259,7 @@ final class Account(
|
|||
Redirect(routes.Account.twoFactor).flashSuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def disableTwoFactor =
|
||||
|
@ -274,7 +274,7 @@ final class Account(
|
|||
Redirect(routes.Account.twoFactor).flashSuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def close =
|
||||
|
@ -395,7 +395,7 @@ final class Account(
|
|||
env.security.reopen.send(user, data.realEmail) inject Redirect(
|
||||
routes.Account.reopenSent(data.realEmail.value)
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ final class Api(
|
|||
private val userApi = env.api.userApi
|
||||
private val gameApi = env.api.gameApi
|
||||
|
||||
implicit private[controllers] val limitedDefault = Zero.instance[ApiResult](Limited)
|
||||
|
||||
private lazy val apiStatusJson = {
|
||||
val api = lila.api.Mobile.Api
|
||||
Json.obj(
|
||||
|
@ -76,7 +74,7 @@ final class Api(
|
|||
env.user.repo nameds usernames map {
|
||||
_.map { env.user.jsonView(_, none) }
|
||||
} map toApiResult map toHttp
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def usersStatus =
|
||||
|
@ -123,9 +121,9 @@ final class Api(
|
|||
UserGamesRateLimitPerUA(~HTTPRequest.userAgent(req), cost = cost, msg = ip.value) {
|
||||
UserGamesRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
||||
run
|
||||
}
|
||||
}
|
||||
}
|
||||
}(fuccess(Limited))
|
||||
}(fuccess(Limited))
|
||||
}(fuccess(Limited))
|
||||
}
|
||||
|
||||
private def gameFlagsFromRequest(req: RequestHeader) =
|
||||
|
@ -174,7 +172,7 @@ final class Api(
|
|||
GameRateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = 1) {
|
||||
lila.mon.api.game.increment(1)
|
||||
gameApi.one(id take lila.game.Game.gameIdSize, gameFlagsFromRequest(req)) map toApiResult
|
||||
}
|
||||
}(fuccess(Limited))
|
||||
}
|
||||
|
||||
private val CrosstableRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
|
@ -192,7 +190,7 @@ final class Api(
|
|||
lila.game.JsonView.crosstableWrites.writes(ct).some
|
||||
}
|
||||
}
|
||||
}
|
||||
}(fuccess(Limited))
|
||||
}
|
||||
|
||||
def currentTournaments =
|
||||
|
@ -348,7 +346,7 @@ final class Api(
|
|||
}
|
||||
}
|
||||
} map toApiResult
|
||||
}
|
||||
}(fuccess(Limited))
|
||||
}
|
||||
|
||||
def CookieBasedApiRequest(js: Context => Fu[ApiResult]) =
|
||||
|
|
|
@ -138,7 +138,7 @@ final class Auth(
|
|||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ final class Auth(
|
|||
lila.security.EmailConfirm.cookie
|
||||
.make(env.lilaCookie, user, newUserEmail.email)(ctx.req)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ final class Auth(
|
|||
env.push.webSubscriptionApi.unsubscribeByUser(user) >>
|
||||
authenticateUser(user) >>-
|
||||
lila.mon.user.auth.passwordResetConfirm("success").increment()
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -422,7 +422,7 @@ final class Auth(
|
|||
env.security.magicLink.send(user, storedEmail) inject Redirect(
|
||||
routes.Auth.magicLinkSent(storedEmail.value)
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
case _ => {
|
||||
lila.mon.user.auth.magicLinkRequest("no_email").increment()
|
||||
|
|
|
@ -198,7 +198,7 @@ final class Challenge(
|
|||
case None => api.setDestUser(c, dest) inject Redirect(routes.Challenge.show(c.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
else notFound
|
||||
}
|
||||
|
@ -249,8 +249,8 @@ final class Challenge(
|
|||
} map (_ as JSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,7 @@ final class Challenge(
|
|||
case false =>
|
||||
BadRequest(jsonError("Challenge not created"))
|
||||
}
|
||||
} dmap (_ as JSON)
|
||||
}(rateLimitedFu) dmap (_ as JSON)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -451,7 +451,7 @@ final class Clas(
|
|||
Redirect(routes.Clas.studentShow(clas.id.value, s.user.username)).flashSuccess {
|
||||
s"A confirmation email was sent to ${email.acceptable.value}. ${s.student.realName} must click the link in the email to release the account."
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
)
|
||||
else
|
||||
|
|
|
@ -36,7 +36,7 @@ final class Export(env: Env) extends LilaController(env) {
|
|||
stream("image/gif") map
|
||||
gameImageCacheSeconds(game)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ final class Export(env: Env) extends LilaController(env) {
|
|||
stream("image/gif") map
|
||||
gameImageCacheSeconds(game)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def legacyPuzzleThumbnail(id: Int) =
|
||||
|
@ -73,7 +73,7 @@ final class Export(env: Env) extends LilaController(env) {
|
|||
res.withHeaders(CACHE_CONTROL -> "max-age=86400")
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
private def gameImageCacheSeconds(game: lila.game.Game)(res: Result): Result = {
|
||||
|
|
|
@ -42,7 +42,7 @@ final class ForumPost(env: Env) extends LilaController(env) with ForumController
|
|||
postApi.makePost(categ, topic, data) map { post =>
|
||||
Redirect(routes.ForumPost.redirect(post.id))
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ final class ForumPost(env: Env) extends LilaController(env) with ForumController
|
|||
postApi.editPost(postId, data.changes, me).map { post =>
|
||||
Redirect(routes.ForumPost.redirect(post.id))
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ final class ForumTopic(env: Env) extends LilaController(env) with ForumControlle
|
|||
topicApi.makeTopic(categ, data, me) map { topic =>
|
||||
Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1))
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ abstract private[controllers] class LilaController(val env: Env)
|
|||
|
||||
protected val keyPages = new KeyPages(env)
|
||||
protected val renderNotFound = keyPages.notFound _
|
||||
protected val rateLimited = Results.TooManyRequests
|
||||
protected val rateLimitedFu = rateLimited.fuccess
|
||||
|
||||
implicit protected def LilaFunitToResult(
|
||||
@nowarn("cat=unused") funit: Funit
|
||||
|
|
|
@ -190,7 +190,7 @@ final class Plan(env: Env)(implicit system: akka.actor.ActorSystem) extends Lila
|
|||
.flatMap(customer => createStripeSession(checkout, customer.id))
|
||||
}
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def payPalIpn =
|
||||
|
|
|
@ -71,7 +71,7 @@ final class Search(env: Env) extends LilaController(env) {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ final class Setup(
|
|||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def hookForm =
|
||||
|
@ -184,7 +184,7 @@ final class Setup(
|
|||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ final class Setup(
|
|||
.hook(hookConfig, Sri(sri), HTTPRequest sid ctx.req, blocking ++ sameOpponents)
|
||||
} yield hookResponse(hookResult)
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,7 @@ final class Setup(
|
|||
BoardApiHookConcurrencyLimitPerUser(me.id)(
|
||||
env.lobby.boardApiHookStream(hook.copy(boardApi = true))
|
||||
)(apiC.sourceToNdJsonOption).fuccess
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
case _ => BadRequest(jsonError("Invalid board API seek")).fuccess
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ final class Setup(
|
|||
Created(env.game.jsonView(pov.game, config.fen)) as JSON
|
||||
}
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
private def process[A](form: Context => Form[A])(op: A => BodyContext[_] => Fu[Pov]) =
|
||||
|
@ -296,7 +296,7 @@ final class Setup(
|
|||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
private[controllers] def redirectPov(pov: Pov)(implicit ctx: Context) = {
|
||||
|
|
|
@ -412,8 +412,8 @@ final class Study(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
private val PgnRateLimitPerIp = new lila.memo.RateLimit[IpAddress](
|
||||
|
@ -438,7 +438,7 @@ final class Study(
|
|||
.fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rateLimitedFu)
|
||||
}
|
||||
|
||||
def chapterPgn(id: String, chapterId: String) =
|
||||
|
|
|
@ -430,7 +430,7 @@ final class Team(
|
|||
You received this message because you are part of the team lichess.org${routes.Team.show(team.id)}."""
|
||||
env.msg.api.multiPost(me, env.team.memberStream.ids(team, MaxPerSecond(50)), full)
|
||||
funit // we don't wait for the stream to complete, it would make lichess time out
|
||||
}
|
||||
}(funit)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -250,9 +250,7 @@ final class Tournament(
|
|||
key = "tournament.ip"
|
||||
)
|
||||
|
||||
private val rateLimited = ornicar.scalalib.Zero.instance[Fu[Result]] {
|
||||
fuccess(Redirect(routes.Tournament.home))
|
||||
}
|
||||
private val rateLimitedCreation = fuccess(Redirect(routes.Tournament.home))
|
||||
|
||||
private[controllers] def rateLimitCreation(me: UserModel, isPrivate: Boolean, req: RequestHeader)(
|
||||
create: => Fu[Result]
|
||||
|
@ -270,8 +268,8 @@ final class Tournament(
|
|||
CreateLimitPerUser(me.id, cost = cost) {
|
||||
CreateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = cost) {
|
||||
create
|
||||
}(rateLimited)
|
||||
}(rateLimited)
|
||||
}(rateLimitedCreation)
|
||||
}(rateLimitedCreation)
|
||||
}
|
||||
|
||||
def create =
|
||||
|
|
|
@ -217,9 +217,6 @@ final class User(
|
|||
key = "user_games.web.ip"
|
||||
)
|
||||
|
||||
implicit val userGamesDefault =
|
||||
ornicar.scalalib.Zero.instance[Fu[Paginator[GameModel]]](fuccess(Paginator.empty[GameModel]))
|
||||
|
||||
private def userGames(
|
||||
u: UserModel,
|
||||
filterName: String,
|
||||
|
@ -241,7 +238,7 @@ final class User(
|
|||
}
|
||||
_ <- env.user.lightUserApi preloadMany pag.currentPageResults.flatMap(_.userIds)
|
||||
} yield pag
|
||||
}
|
||||
}(fuccess(Paginator.empty[GameModel]))
|
||||
}
|
||||
|
||||
def list =
|
||||
|
|
|
@ -11,7 +11,7 @@ final class HttpFilter(env: Env)(implicit val mat: Materializer) extends Filter
|
|||
private val httpMon = lila.mon.http
|
||||
private val net = env.net
|
||||
private val logger = lila.log("http")
|
||||
private val logRequests = false
|
||||
private val logRequests = true
|
||||
|
||||
def apply(nextFilter: RequestHeader => Fu[Result])(req: RequestHeader): Fu[Result] =
|
||||
if (HTTPRequest isAssets req) nextFilter(req) dmap { result =>
|
||||
|
@ -34,11 +34,10 @@ final class HttpFilter(env: Env)(implicit val mat: Materializer) extends Filter
|
|||
val actionName = HTTPRequest actionName req
|
||||
val reqTime = nowMillis - startTime
|
||||
val statusCode = result.header.status
|
||||
if (env.isDev && logRequests) logger.info(s"$statusCode $req $actionName ${reqTime}ms")
|
||||
else {
|
||||
val client = HTTPRequest clientName req
|
||||
httpMon.time(actionName, client, req.method, statusCode).record(reqTime)
|
||||
}
|
||||
val client = HTTPRequest clientName req
|
||||
if (env.isDev && logRequests)
|
||||
logger.info(s"$statusCode $client $req ${req.method} $actionName ${reqTime}ms")
|
||||
else httpMon.time(actionName, client, req.method, statusCode).record(reqTime)
|
||||
}
|
||||
|
||||
private def redirectWrongDomain(req: RequestHeader): Option[Result] =
|
||||
|
|
|
@ -50,7 +50,7 @@ final private class Limiter(
|
|||
case Work.Sender(Some(userId), _, _, _) => requesterApi.countToday(userId) map (_ < maxPerDay)
|
||||
case Work.Sender(_, Some(ip), _, _) =>
|
||||
fuccess {
|
||||
RequestLimitPerIP(ip, cost = 1)(true)
|
||||
RequestLimitPerIP(ip, cost = 1)(true)(false)
|
||||
}
|
||||
case _ => fuFalse
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package lila.lobby
|
||||
|
||||
import ornicar.scalalib.Zero
|
||||
import play.api.libs.json._
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Promise
|
||||
|
@ -153,8 +152,8 @@ final class LobbySocket(
|
|||
key = "lobby.hook_pool.member"
|
||||
)
|
||||
|
||||
private def HookPoolLimit[A: Zero](member: Member, cost: Int, msg: => String)(op: => A) =
|
||||
poolLimitPerSri(k = member.sri.value, cost = cost, msg = msg)(op)
|
||||
private def HookPoolLimit(member: Member, cost: Int, msg: => String)(op: => Unit) =
|
||||
poolLimitPerSri(k = member.sri.value, cost = cost, msg = msg)(op) {}
|
||||
|
||||
def controller(member: Member): SocketController = {
|
||||
case ("join", o) if !member.bot =>
|
||||
|
|
|
@ -27,10 +27,10 @@ final class RateLimit[K](
|
|||
|
||||
def chargeable[A](k: K, cost: Cost = 1, msg: => String = "")(
|
||||
op: Charge => A
|
||||
)(implicit default: Zero[A]): A =
|
||||
apply(k, cost, msg) { op(c => apply(k, c, s"charge: $msg")(())) }
|
||||
)(default: => A): A =
|
||||
apply(k, cost, msg) { op(c => apply(k, c, s"charge: $msg") {} {}) }(default)
|
||||
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(implicit default: Zero[A]): A =
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(default: => A): A =
|
||||
storage getIfPresent k match {
|
||||
case None =>
|
||||
storage.put(k, cost -> makeClearAt)
|
||||
|
@ -44,7 +44,7 @@ final class RateLimit[K](
|
|||
case _ if enforce =>
|
||||
if (log) logger.info(s"$credits/$duration $k cost: $cost $msg")
|
||||
monitor.increment()
|
||||
default.zero
|
||||
default
|
||||
case _ =>
|
||||
op
|
||||
}
|
||||
|
@ -57,11 +57,9 @@ object RateLimit {
|
|||
|
||||
trait RateLimiter[K] {
|
||||
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(implicit default: Zero[A]): A
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(default: => A): A
|
||||
|
||||
def chargeable[A](k: K, cost: Cost = 1, msg: => String = "")(op: Charge => A)(
|
||||
implicit default: Zero[A]
|
||||
): A
|
||||
def chargeable[A](k: K, cost: Cost = 1, msg: => String = "")(op: Charge => A)(default: => A): A
|
||||
}
|
||||
|
||||
def composite[K](
|
||||
|
@ -85,18 +83,16 @@ object RateLimit {
|
|||
|
||||
new RateLimiter[K] {
|
||||
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(implicit default: Zero[A]): A = {
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(default: => A): A = {
|
||||
val accepted = limiters.foldLeft(true) {
|
||||
case (true, limiter) => limiter(k, cost, msg)(true)
|
||||
case (true, limiter) => limiter(k, cost, msg)(true)(false)
|
||||
case (false, _) => false
|
||||
}
|
||||
if (accepted) op else default.zero
|
||||
if (accepted) op else default
|
||||
}
|
||||
|
||||
def chargeable[A](k: K, cost: Cost = 1, msg: => String = "")(op: Charge => A)(
|
||||
implicit default: Zero[A]
|
||||
): A = {
|
||||
apply(k, cost, msg) { op(c => apply(k, c, s"charge: $msg")(())) }
|
||||
def chargeable[A](k: K, cost: Cost = 1, msg: => String = "")(op: Charge => A)(default: => A): A = {
|
||||
apply(k, cost, msg) { op(c => apply(k, c, s"charge: $msg") {} {}) }(default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ final private class MsgSecurity(
|
|||
else {
|
||||
val limiter = if (isNew) CreateLimitPerUser else ReplyLimitPerUser
|
||||
val cost = if (user.isVerified) 1 else 5
|
||||
!limiter(user.id, cost = cost)(true) ?? fuccess(Limit.some)
|
||||
!limiter(user.id, cost = cost)(true)(false) ?? fuccess(Limit.some)
|
||||
}
|
||||
|
||||
private def isSpam(text: String): Fu[Option[Verdict]] =
|
||||
|
|
|
@ -166,7 +166,7 @@ final class RelationApi(
|
|||
(config.maxFollow < nb) ?? {
|
||||
limitFollowRateLimiter(u) {
|
||||
fetchFollowing(u) flatMap userRepo.filterClosedOrInactiveIds(DateTime.now.minusDays(90))
|
||||
} flatMap {
|
||||
}(fuccess(Nil)) flatMap {
|
||||
case Nil => repo.drop(u, true, nb - config.maxFollow.value)
|
||||
case inactiveIds =>
|
||||
repo.unfollowMany(u, inactiveIds) >>-
|
||||
|
|
|
@ -49,7 +49,8 @@ final private class Rematcher(
|
|||
case Pov(game, color) if game.playerCouldRematch =>
|
||||
if (isOffering(!pov) || game.opponent(color).isAi)
|
||||
rematches.of(game.id).fold(rematchJoin(pov))(rematchExists(pov))
|
||||
else if (!declined.get(pov.flip.fullId) && rateLimit(pov.fullId)(true)) fuccess(rematchCreate(pov))
|
||||
else if (!declined.get(pov.flip.fullId) && rateLimit(pov.fullId)(true)(false))
|
||||
fuccess(rematchCreate(pov))
|
||||
else fuccess(List(Event.RematchOffer(by = none)))
|
||||
case _ => fuccess(List(Event.ReloadOwner))
|
||||
}
|
||||
|
|
|
@ -148,14 +148,14 @@ object EmailConfirm {
|
|||
key = "email.confirms.email"
|
||||
)
|
||||
|
||||
def rateLimit[A: Zero](userEmail: UserEmail, req: RequestHeader)(run: => Fu[A]): Fu[A] =
|
||||
def rateLimit[A: Zero](userEmail: UserEmail, req: RequestHeader)(run: => Fu[A])(default: => Fu[A]): Fu[A] =
|
||||
rateLimitPerUser(userEmail.username, cost = 1) {
|
||||
rateLimitPerEmail(userEmail.email.value, cost = 1) {
|
||||
rateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = 1) {
|
||||
run
|
||||
}
|
||||
}
|
||||
}
|
||||
}(default)
|
||||
}(default)
|
||||
}(default)
|
||||
|
||||
object Help {
|
||||
|
||||
|
|
|
@ -77,12 +77,14 @@ object MagicLink {
|
|||
key = "email.confirms.email"
|
||||
)
|
||||
|
||||
def rateLimit[A: Zero](user: User, email: EmailAddress, req: RequestHeader)(run: => Fu[A]): Fu[A] =
|
||||
def rateLimit[A: Zero](user: User, email: EmailAddress, req: RequestHeader)(
|
||||
run: => Fu[A]
|
||||
)(default: => Fu[A]): Fu[A] =
|
||||
rateLimitPerUser(user.id, cost = 1) {
|
||||
rateLimitPerEmail(email.value, cost = 1) {
|
||||
rateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = 1) {
|
||||
run
|
||||
}
|
||||
}
|
||||
}
|
||||
}(default)
|
||||
}(default)
|
||||
}(default)
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ final class Signup(
|
|||
authLog(data.username, data.email, "Signup recaptcha fail")
|
||||
fuccess(Signup.Bad(forms.signup.website fill data))
|
||||
case true =>
|
||||
rateLimit(data.username, if (data.recaptchaResponse.isDefined) 1 else 2) {
|
||||
signupRateLimit(data.username, if (data.recaptchaResponse.isDefined) 1 else 2) {
|
||||
MustConfirmEmail(data.fingerPrint) flatMap {
|
||||
mustConfirm =>
|
||||
lila.mon.user.register.count(none)
|
||||
|
@ -113,7 +113,7 @@ final class Signup(
|
|||
forms.signup.mobile.bindFromRequest.fold[Fu[Signup.Result]](
|
||||
err => fuccess(Signup.Bad(err tap signupErrLog)),
|
||||
data =>
|
||||
rateLimit(data.username, cost = 2) {
|
||||
signupRateLimit(data.username, cost = 2) {
|
||||
val email = emailAddressValidator
|
||||
.validate(data.realEmail) err s"Invalid email ${data.email}"
|
||||
val mustConfirm = MustConfirmEmail.YesBecauseMobile
|
||||
|
@ -137,12 +137,10 @@ final class Signup(
|
|||
}
|
||||
)
|
||||
|
||||
implicit private val ResultZero = ornicar.scalalib.Zero.instance[Signup.Result](Signup.RateLimited)
|
||||
|
||||
private def HasherRateLimit =
|
||||
PasswordHasher.rateLimit[Signup.Result](enforce = netConfig.rateLimit) _
|
||||
|
||||
private lazy val rateLimitPerIP = RateLimit.composite[IpAddress](
|
||||
private lazy val signupRateLimitPerIP = RateLimit.composite[IpAddress](
|
||||
name = "Accounts per IP",
|
||||
key = "account.create.ip",
|
||||
enforce = netConfig.rateLimit.value
|
||||
|
@ -151,12 +149,14 @@ final class Signup(
|
|||
("slow", 150, 1 day)
|
||||
)
|
||||
|
||||
private def rateLimit(username: String, cost: Int)(
|
||||
private val rateLimitDefault = fuccess(Signup.RateLimited)
|
||||
|
||||
private def signupRateLimit(username: String, cost: Int)(
|
||||
f: => Fu[Signup.Result]
|
||||
)(implicit req: RequestHeader): Fu[Signup.Result] =
|
||||
HasherRateLimit(username, req) { _ =>
|
||||
rateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = cost)(f)
|
||||
}
|
||||
signupRateLimitPerIP(HTTPRequest lastRemoteAddress req, cost = cost)(f)(rateLimitDefault)
|
||||
}(rateLimitDefault)
|
||||
|
||||
private def logSignup(
|
||||
req: RequestHeader,
|
||||
|
|
|
@ -51,7 +51,7 @@ final class Env(
|
|||
.buildAsyncFuture(_ => repo.allCreatedFeaturable)
|
||||
}
|
||||
|
||||
val featurable = new SimulIsFeaturable((simul: Simul) => featureLimiter(simul.hostId)(true))
|
||||
val featurable = new SimulIsFeaturable((simul: Simul) => featureLimiter(simul.hostId)(true)(false))
|
||||
|
||||
private val featureLimiter = new lila.memo.RateLimit[lila.user.User.ID](
|
||||
credits = config.featureViews.value,
|
||||
|
|
|
@ -151,7 +151,7 @@ final class SlackApi(
|
|||
channel = rooms.tavernBots
|
||||
)
|
||||
)
|
||||
}
|
||||
}(funit)
|
||||
|
||||
def commReportBurst(user: User): Funit =
|
||||
client(
|
||||
|
|
|
@ -38,5 +38,5 @@ final private class SlackClient(ws: WSClient, url: Secret)(implicit ec: scala.co
|
|||
case res if res.status == 200 => funit
|
||||
case res => fufail(s"[slack] $url $msg ${res.status} ${res.body}")
|
||||
}
|
||||
}
|
||||
}(funit)
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ final private class StudyInvite(
|
|||
)
|
||||
val notification = Notification.make(Notification.Notifies(invited.id), notificationContent)
|
||||
notifyApi.addNotification(notification)
|
||||
}
|
||||
}(funit)
|
||||
} yield ()
|
||||
|
||||
def admin(study: Study, user: User): Funit =
|
||||
|
|
|
@ -206,7 +206,7 @@ final private class StudySocket(
|
|||
isPresent = userId => isPresent(studyId, userId),
|
||||
onError = err => send(P.Out.tellSri(w.sri, makeMessage("error", err)))
|
||||
)
|
||||
}
|
||||
}(funit)
|
||||
case "relaySync" =>
|
||||
who foreach { w =>
|
||||
Bus.publish(actorApi.RelayToggle(studyId, ~(o \ "d").asOpt[Boolean], w), "relayToggle")
|
||||
|
|
|
@ -78,7 +78,6 @@ object PasswordHasher {
|
|||
|
||||
import scala.concurrent.duration._
|
||||
import play.api.mvc.RequestHeader
|
||||
import ornicar.scalalib.Zero
|
||||
import lila.memo.RateLimit
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
|
||||
|
@ -110,9 +109,9 @@ object PasswordHasher {
|
|||
key = "password.hashes.global"
|
||||
)
|
||||
|
||||
def rateLimit[A: Zero](
|
||||
def rateLimit[A](
|
||||
enforce: lila.common.config.RateLimit
|
||||
)(username: String, req: RequestHeader)(run: RateLimit.Charge => Fu[A]): Fu[A] =
|
||||
)(username: String, req: RequestHeader)(run: RateLimit.Charge => Fu[A])(default: => Fu[A]): Fu[A] =
|
||||
if (enforce.value) {
|
||||
val cost = 1
|
||||
val ip = HTTPRequest lastRemoteAddress req
|
||||
|
@ -121,9 +120,9 @@ object PasswordHasher {
|
|||
rateLimitPerUA(~HTTPRequest.userAgent(req), cost = cost, msg = ip.value) {
|
||||
rateLimitGlobal("-", cost = cost, msg = ip.value) {
|
||||
run(charge)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(default)
|
||||
}(default)
|
||||
}(default)
|
||||
}(default)
|
||||
} else run(_ => ())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue