type safe IP addresses
parent
560b5af4c1
commit
cd0fed508c
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
|
||||
object Api extends LilaController {
|
||||
|
||||
|
@ -39,14 +39,14 @@ object Api extends LilaController {
|
|||
userApi one name map toApiResult
|
||||
}
|
||||
|
||||
private val UsersRateLimitGlobal = new lila.memo.RateLimit(
|
||||
private val UsersRateLimitGlobal = new lila.memo.RateLimit[String](
|
||||
credits = 1000,
|
||||
duration = 1 minute,
|
||||
name = "team users API global",
|
||||
key = "team_users.api.global"
|
||||
)
|
||||
|
||||
private val UsersRateLimitPerIP = new lila.memo.RateLimit(
|
||||
private val UsersRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 1000,
|
||||
duration = 10 minutes,
|
||||
name = "team users API per IP",
|
||||
|
@ -59,7 +59,7 @@ object Api extends LilaController {
|
|||
val cost = page * nb + 10
|
||||
val ip = HTTPRequest lastRemoteAddress ctx.req
|
||||
UsersRateLimitPerIP(ip, cost = cost) {
|
||||
UsersRateLimitGlobal("-", cost = cost, msg = ip) {
|
||||
UsersRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
||||
lila.mon.api.teamUsers.cost(cost)
|
||||
(get("team") ?? Env.team.api.team).flatMap {
|
||||
_ ?? { team =>
|
||||
|
@ -75,7 +75,7 @@ object Api extends LilaController {
|
|||
val ip = HTTPRequest lastRemoteAddress ctx.req
|
||||
val cost = usernames.size / 4
|
||||
UsersRateLimitPerIP(ip, cost = cost) {
|
||||
UsersRateLimitGlobal("-", cost = cost, msg = ip) {
|
||||
UsersRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
||||
lila.mon.api.users.cost(1)
|
||||
lila.user.UserRepo nameds usernames map {
|
||||
_.map { Env.user.jsonView(_, none) }
|
||||
|
@ -101,21 +101,21 @@ object Api extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit(
|
||||
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 10 * 1000,
|
||||
duration = 10 minutes,
|
||||
name = "user games API per IP",
|
||||
key = "user_games.api.ip"
|
||||
)
|
||||
|
||||
private val UserGamesRateLimitPerUA = new lila.memo.RateLimit(
|
||||
private val UserGamesRateLimitPerUA = new lila.memo.RateLimit[String](
|
||||
credits = 10 * 1000,
|
||||
duration = 5 minutes,
|
||||
name = "user games API per UA",
|
||||
key = "user_games.api.ua"
|
||||
)
|
||||
|
||||
private val UserGamesRateLimitGlobal = new lila.memo.RateLimit(
|
||||
private val UserGamesRateLimitGlobal = new lila.memo.RateLimit[String](
|
||||
credits = 10 * 1000,
|
||||
duration = 1 minute,
|
||||
name = "user games API global",
|
||||
|
@ -128,8 +128,8 @@ object Api extends LilaController {
|
|||
val cost = page * nb + 10
|
||||
val ip = HTTPRequest lastRemoteAddress ctx.req
|
||||
UserGamesRateLimitPerIP(ip, cost = cost) {
|
||||
UserGamesRateLimitPerUA(~HTTPRequest.userAgent(ctx.req), cost = cost, msg = ip) {
|
||||
UserGamesRateLimitGlobal("-", cost = cost, msg = ip) {
|
||||
UserGamesRateLimitPerUA(~HTTPRequest.userAgent(ctx.req), cost = cost, msg = ip.value) {
|
||||
UserGamesRateLimitGlobal("-", cost = cost, msg = ip.value) {
|
||||
lila.mon.api.userGames.cost(cost)
|
||||
lila.user.UserRepo named name flatMap {
|
||||
_ ?? { user =>
|
||||
|
@ -153,7 +153,7 @@ object Api extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private val GameRateLimitPerIP = new lila.memo.RateLimit(
|
||||
private val GameRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 100,
|
||||
duration = 1 minute,
|
||||
name = "game API per IP",
|
||||
|
|
|
@ -7,7 +7,7 @@ import play.api.Play.current
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.{ LilaCookie, HTTPRequest }
|
||||
import lila.common.{ LilaCookie, HTTPRequest, IpAddress }
|
||||
import lila.user.{ UserRepo, User => UserModel }
|
||||
import views._
|
||||
|
||||
|
@ -98,7 +98,7 @@ object Auth extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private def mustConfirmEmailByIP(ip: String, username: String): Fu[Boolean] =
|
||||
private def mustConfirmEmailByIP(ip: IpAddress, username: String): Fu[Boolean] =
|
||||
fuccess(username.toLowerCase.contains("argeskent")) >>|
|
||||
api.recentByIpExists(ip) >>|
|
||||
Mod.ipIntelCache.get(ip).map(80 <).recover { case _: Exception => false }
|
||||
|
|
|
@ -40,7 +40,7 @@ object Export extends LilaController {
|
|||
})
|
||||
}
|
||||
|
||||
private val PngRateLimitGlobal = new lila.memo.RateLimit(
|
||||
private val PngRateLimitGlobal = new lila.memo.RateLimit[String](
|
||||
credits = 60,
|
||||
duration = 1 minute,
|
||||
name = "export PGN global",
|
||||
|
@ -49,7 +49,7 @@ object Export extends LilaController {
|
|||
|
||||
def png(id: String) = Open { implicit ctx =>
|
||||
OnlyHumansAndFacebookOrTwitter {
|
||||
PngRateLimitGlobal("-", msg = HTTPRequest lastRemoteAddress ctx.req) {
|
||||
PngRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
|
||||
lila.mon.export.png.game()
|
||||
OptionFuResult(GameRepo game id) { game =>
|
||||
env.pngExport fromGame game map { stream =>
|
||||
|
@ -65,7 +65,7 @@ object Export extends LilaController {
|
|||
|
||||
def puzzlePng(id: Int) = Open { implicit ctx =>
|
||||
OnlyHumansAndFacebookOrTwitter {
|
||||
PngRateLimitGlobal("-", msg = HTTPRequest lastRemoteAddress ctx.req) {
|
||||
PngRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
|
||||
lila.mon.export.png.puzzle()
|
||||
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
|
||||
env.pngExport(
|
||||
|
|
|
@ -67,7 +67,7 @@ object Fishnet extends LilaController {
|
|||
logger.warn(s"Malformed request: $err\n${req.body}")
|
||||
BadRequest(jsonError(JsError toJson err)).fuccess
|
||||
},
|
||||
data => api.authenticateClient(data, clientIp(req)) flatMap {
|
||||
data => api.authenticateClient(data, HTTPRequest lastRemoteAddress req) flatMap {
|
||||
case Failure(msg) => {
|
||||
val ip = HTTPRequest.lastRemoteAddress(req)
|
||||
logger.info(s"Unauthorized key: ${data.fishnet.apikey} ip: $ip | ${msg.getMessage}")
|
||||
|
@ -81,8 +81,4 @@ object Fishnet extends LilaController {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
private def clientIp(req: RequestHeader) = lila.fishnet.Client.IpAddress {
|
||||
HTTPRequest lastRemoteAddress req
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package controllers
|
||||
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
import scala.concurrent.duration._
|
||||
import views._
|
||||
|
||||
object ForumPost extends LilaController with ForumController {
|
||||
|
||||
private val CreateRateLimit = new lila.memo.RateLimit(4, 5 minutes,
|
||||
private val CreateRateLimit = new lila.memo.RateLimit[IpAddress](4, 5 minutes,
|
||||
name = "forum create post",
|
||||
key = "forum.post")
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ package controllers
|
|||
import scala.concurrent.duration._
|
||||
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
import lila.forum.CategRepo
|
||||
import play.api.libs.json._
|
||||
import views._
|
||||
|
||||
object ForumTopic extends LilaController with ForumController {
|
||||
|
||||
private val CreateRateLimit = new lila.memo.RateLimit(2, 5 minutes,
|
||||
private val CreateRateLimit = new lila.memo.RateLimit[IpAddress](2, 5 minutes,
|
||||
name = "forum create topic",
|
||||
key = "forum.topic")
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import play.api.mvc.WebSocket.FrameFormatter
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
|
||||
trait LilaSocket { self: LilaController =>
|
||||
|
||||
|
@ -31,7 +31,7 @@ trait LilaSocket { self: LilaController =>
|
|||
f(ctx).map(_ toRight notFoundResponse)
|
||||
}
|
||||
|
||||
protected def SocketOptionLimited[A: FrameFormatter](limiter: lila.memo.RateLimit, name: String)(f: Context => Fu[Option[Pipe[A]]]) =
|
||||
protected def SocketOptionLimited[A: FrameFormatter](limiter: lila.memo.RateLimit[IpAddress], name: String)(f: Context => Fu[Option[Pipe[A]]]) =
|
||||
rateLimitedSocket[A](limiter, name) { ctx =>
|
||||
f(ctx).map(_ toRight notFoundResponse)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ trait LilaSocket { self: LilaController =>
|
|||
|
||||
private val rateLimitLogger = lila.log("ratelimit")
|
||||
|
||||
private def rateLimitedSocket[A: FrameFormatter](limiter: lila.memo.RateLimit, name: String)(f: AcceptType[A]): WebSocket[A, A] =
|
||||
private def rateLimitedSocket[A: FrameFormatter](limiter: lila.memo.RateLimit[IpAddress], name: String)(f: AcceptType[A]): WebSocket[A, A] =
|
||||
WebSocket[A, A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap { ctx =>
|
||||
|
|
|
@ -5,6 +5,7 @@ import play.api.mvc._
|
|||
import play.twirl.api.Html
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.IpAddress
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import views._
|
||||
|
@ -42,7 +43,7 @@ object Lobby extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
private val MessageLimitPerIP = new lila.memo.RateLimit(
|
||||
private val MessageLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 40,
|
||||
duration = 10 seconds,
|
||||
name = "lobby socket message per IP",
|
||||
|
|
|
@ -90,28 +90,12 @@ object Main extends LilaController {
|
|||
}
|
||||
|
||||
def jslog(id: String) = Open { ctx =>
|
||||
val known = ctx.me.??(_.engine)
|
||||
val referer = HTTPRequest.referer(ctx.req)
|
||||
val name = get("n", ctx.req) | "?"
|
||||
lila.mon.cheat.cssBot()
|
||||
ctx.userId.ifTrue(!known && name != "ceval") ?? {
|
||||
Env.report.api.autoBotReport(_, referer, name)
|
||||
}
|
||||
def doLog = lila.log("cheat").branch("jslog").info(
|
||||
s"${ctx.req.remoteAddress} ${referer | "?"} ${ctx.userId | "anon"} $name"
|
||||
Env.round.selfReport(
|
||||
userId = ctx.userId,
|
||||
ip = HTTPRequest lastRemoteAddress ctx.req,
|
||||
fullId = id,
|
||||
name = get("n", ctx.req) | "?"
|
||||
)
|
||||
if (id == "________") doLog
|
||||
else lila.game.GameRepo pov id foreach {
|
||||
_ foreach { pov =>
|
||||
if (!known) doLog
|
||||
if (name == "ceval" || name == "rcb" || name == "ccs")
|
||||
Env.round.roundMap ! lila.hub.actorApi.map.Tell(
|
||||
pov.gameId,
|
||||
lila.round.actorApi.round.Cheat(pov.color)
|
||||
)
|
||||
else lila.game.GameRepo.setBorderAlert(pov)
|
||||
}
|
||||
}
|
||||
Ok.fuccess
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package controllers
|
|||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.user.{ UserRepo, User => UserModel }
|
||||
import lila.common.IpAddress
|
||||
import views._
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
@ -153,7 +154,7 @@ object Mod extends LilaController {
|
|||
}
|
||||
|
||||
private[controllers] val ipIntelCache =
|
||||
Env.memo.asyncCache.multi[String, Int](
|
||||
Env.memo.asyncCache.multi[IpAddress, Int](
|
||||
name = "ipIntel",
|
||||
f = ip => {
|
||||
import play.api.libs.ws.WS
|
||||
|
@ -177,7 +178,7 @@ object Mod extends LilaController {
|
|||
)
|
||||
|
||||
def ipIntel(ip: String) = Secure(_.IpBan) { ctx => me =>
|
||||
ipIntelCache.get(ip).map { Ok(_) }.recover {
|
||||
ipIntelCache.get(IpAddress(ip)).map { Ok(_) }.recover {
|
||||
case e: Exception => InternalServerError(e.getMessage)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ object Plan extends LilaController {
|
|||
cents = lila.plan.Cents(ipn.grossCents),
|
||||
name = ipn.name,
|
||||
txnId = ipn.txnId,
|
||||
ip = lila.common.HTTPRequest.lastRemoteAddress(req),
|
||||
ip = lila.common.HTTPRequest.lastRemoteAddress(req).value,
|
||||
key = lila.plan.PayPalIpnKey(get("key", req) | "N/A")
|
||||
) inject Ok
|
||||
)
|
||||
|
|
|
@ -3,7 +3,7 @@ package controllers
|
|||
import scala.concurrent.duration._
|
||||
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
import views._
|
||||
|
||||
object Search extends LilaController {
|
||||
|
@ -11,14 +11,14 @@ object Search extends LilaController {
|
|||
private def env = Env.gameSearch
|
||||
def searchForm = env.forms.search
|
||||
|
||||
private val RateLimitGlobal = new lila.memo.RateLimit(
|
||||
private val RateLimitGlobal = new lila.memo.RateLimit[String](
|
||||
credits = 50,
|
||||
duration = 1 minute,
|
||||
name = "search games global",
|
||||
key = "search.games.global"
|
||||
)
|
||||
|
||||
private val RateLimitPerIP = new lila.memo.RateLimit(
|
||||
private val RateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 50,
|
||||
duration = 5 minutes,
|
||||
name = "search games per IP",
|
||||
|
|
|
@ -9,7 +9,7 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.api.{ Context, BodyContext }
|
||||
import lila.app._
|
||||
import lila.common.{ HTTPRequest, LilaCookie }
|
||||
import lila.common.{ HTTPRequest, LilaCookie, IpAddress }
|
||||
import lila.game.{ GameRepo, Pov, AnonCookie }
|
||||
import lila.setup.Processor.HookResult
|
||||
import lila.setup.ValidFen
|
||||
|
@ -20,7 +20,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
private def env = Env.setup
|
||||
|
||||
private val PostRateLimit = new lila.memo.RateLimit(5, 1 minute,
|
||||
private val PostRateLimit = new lila.memo.RateLimit[IpAddress](5, 1 minute,
|
||||
name = "setup post",
|
||||
key = "setup_post")
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ IpAddress, HTTPRequest }
|
||||
import lila.study.Study.WithChapter
|
||||
import lila.study.{ Chapter, Order, Study => StudyModel }
|
||||
import views._
|
||||
|
@ -212,14 +212,14 @@ object Study extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private val CloneLimitPerUser = new lila.memo.RateLimit(
|
||||
private val CloneLimitPerUser = new lila.memo.RateLimit[lila.user.User.ID](
|
||||
credits = 10 * 3,
|
||||
duration = 24 hour,
|
||||
name = "clone study per user",
|
||||
key = "clone_study.user"
|
||||
)
|
||||
|
||||
private val CloneLimitPerIP = new lila.memo.RateLimit(
|
||||
private val CloneLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 20 * 3,
|
||||
duration = 24 hour,
|
||||
name = "clone study per IP",
|
||||
|
@ -242,7 +242,7 @@ object Study extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private val PgnRateLimitGlobal = new lila.memo.RateLimit(
|
||||
private val PgnRateLimitGlobal = new lila.memo.RateLimit[String](
|
||||
credits = 30,
|
||||
duration = 1 minute,
|
||||
name = "export study PGN global",
|
||||
|
@ -251,7 +251,7 @@ object Study extends LilaController {
|
|||
|
||||
def pgn(id: String) = Open { implicit ctx =>
|
||||
OnlyHumans {
|
||||
PgnRateLimitGlobal("-", msg = HTTPRequest lastRemoteAddress ctx.req) {
|
||||
PgnRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
|
||||
OptionFuResult(env.api byId id) { study =>
|
||||
CanViewResult(study) {
|
||||
lila.mon.export.pgn.study()
|
||||
|
|
|
@ -7,7 +7,7 @@ import scala.concurrent.duration._
|
|||
import lila.api.BodyContext
|
||||
import lila.app._
|
||||
import lila.app.mashup.GameFilterMenu
|
||||
import lila.common.HTTPRequest
|
||||
import lila.common.{ IpAddress, HTTPRequest }
|
||||
import lila.common.paginator.Paginator
|
||||
import lila.game.{ GameRepo, Game => GameModel }
|
||||
import lila.rating.PerfType
|
||||
|
@ -136,7 +136,7 @@ object User extends LilaController {
|
|||
searchForm = GameFilterMenu.searchForm(userGameSearch, filters.current)(ctx.body)
|
||||
} yield html.user.show(u, info, pag, filters, searchForm, relation, notes, followable, blocked)
|
||||
|
||||
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit(
|
||||
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 500,
|
||||
duration = 10 minutes,
|
||||
name = "user games web/mobile per IP",
|
||||
|
|
|
@ -32,8 +32,9 @@ object HTTPRequest {
|
|||
|
||||
def referer(req: RequestHeader): Option[String] = req.headers get HeaderNames.REFERER
|
||||
|
||||
def lastRemoteAddress(req: RequestHeader): String =
|
||||
def lastRemoteAddress(req: RequestHeader) = IpAddress {
|
||||
req.remoteAddress.split(", ").lastOption | req.remoteAddress
|
||||
}
|
||||
|
||||
def sid(req: RequestHeader): Option[String] = req.session get LilaCookie.sessionId
|
||||
|
||||
|
|
|
@ -24,4 +24,6 @@ object Iso {
|
|||
implicit def isoIdentity[A]: Iso[A, A] = apply(identity[A] _, identity[A] _)
|
||||
|
||||
implicit val stringIsoIdentity: Iso[String, String] = isoIdentity[String]
|
||||
|
||||
implicit val ipAddressIso = string[IpAddress](IpAddress.apply, _.value)
|
||||
}
|
||||
|
|
|
@ -8,3 +8,15 @@ case class ApiVersion(value: Int) extends AnyVal with IntValue {
|
|||
case class AssetVersion(value: Int) extends AnyVal with IntValue
|
||||
|
||||
case class MaxPerPage(value: Int) extends AnyVal with IntValue
|
||||
|
||||
case class IpAddress(value: String) extends AnyVal with StringValue
|
||||
|
||||
object IpAddress {
|
||||
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
|
||||
private val ipv4Regex = """^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
|
||||
// ipv6 address in standard form (no compression, no leading zeros)
|
||||
private val ipv6Regex = """^((0|[1-9a-f][0-9a-f]{0,3}):){7}(0|[1-9a-f][0-9a-f]{0,3})""".r
|
||||
|
||||
def isv4(a: IpAddress) = ipv4Regex matches a.value
|
||||
def isv6(a: IpAddress) = ipv6Regex matches a.value
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import org.joda.time.DateTime
|
|||
import reactivemongo.bson._
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import lila.common.Iso
|
||||
import lila.common.Iso._
|
||||
import lila.common.{ Iso, IpAddress }
|
||||
|
||||
trait Handlers {
|
||||
|
||||
|
@ -58,4 +58,6 @@ trait Handlers {
|
|||
reader.read(v.get)
|
||||
}
|
||||
}
|
||||
|
||||
implicit val ipAddressHandler = isoHandler[IpAddress, String, BSONString](ipAddressIso)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package lila.fishnet
|
||||
|
||||
import lila.db.BSON.{ BSONJodaDateTimeHandler, stringAnyValHandler }
|
||||
import lila.common.IpAddress
|
||||
import reactivemongo.bson._
|
||||
|
||||
import chess.format.FEN
|
||||
|
@ -12,7 +13,7 @@ private object BSONHandlers {
|
|||
implicit val ClientVersionBSONHandler = stringAnyValHandler[Client.Version](_.value, Client.Version.apply)
|
||||
implicit val ClientPythonBSONHandler = stringAnyValHandler[Client.Python](_.value, Client.Python.apply)
|
||||
implicit val ClientUserIdBSONHandler = stringAnyValHandler[Client.UserId](_.value, Client.UserId.apply)
|
||||
implicit val ClientIpAddressBSONHandler = stringAnyValHandler[Client.IpAddress](_.value, Client.IpAddress.apply)
|
||||
implicit val ClientIpAddressBSONHandler = stringAnyValHandler[IpAddress](_.value, IpAddress.apply)
|
||||
|
||||
implicit val ClientSkillBSONHandler = new BSONHandler[BSONString, Client.Skill] {
|
||||
def read(x: BSONString) = Client.Skill byKey x.value err s"Invalid client skill ${x.value}"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package lila.fishnet
|
||||
|
||||
import lila.common.IpAddress
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
case class Client(
|
||||
|
@ -42,7 +44,6 @@ object Client {
|
|||
case class Version(value: String) extends AnyVal with StringValue
|
||||
case class Python(value: String) extends AnyVal with StringValue
|
||||
case class UserId(value: String) extends AnyVal with StringValue
|
||||
case class IpAddress(value: String) extends AnyVal with StringValue
|
||||
case class Engine(name: String)
|
||||
case class Engines(stockfish: Engine)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.joda.time.DateTime
|
|||
import reactivemongo.bson._
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import lila.common.IpAddress
|
||||
import Client.Skill
|
||||
import lila.db.dsl._
|
||||
import lila.hub.FutureSequencer
|
||||
|
@ -27,7 +28,7 @@ final class FishnetApi(
|
|||
|
||||
def keyExists(key: Client.Key) = repo.getEnabledClient(key).map(_.isDefined)
|
||||
|
||||
def authenticateClient(req: JsonApi.Request, ip: Client.IpAddress): Fu[Try[Client]] = {
|
||||
def authenticateClient(req: JsonApi.Request, ip: IpAddress): Fu[Try[Client]] = {
|
||||
if (offlineMode) repo.getOfflineClient map some
|
||||
else repo.getEnabledClient(req.fishnet.apikey)
|
||||
} map {
|
||||
|
|
|
@ -6,10 +6,10 @@ import play.api.libs.json._
|
|||
import chess.format.{ Uci, FEN }
|
||||
import chess.variant.Variant
|
||||
|
||||
import lila.common.Maths
|
||||
import lila.common.{ Maths, IpAddress }
|
||||
import lila.fishnet.{ Work => W }
|
||||
import lila.tree.Eval.{ Cp, Mate }
|
||||
import lila.tree.Eval.JsonHandlers._
|
||||
import lila.tree.Eval.{ Cp, Mate }
|
||||
|
||||
object JsonApi {
|
||||
|
||||
|
@ -17,7 +17,7 @@ object JsonApi {
|
|||
val fishnet: Request.Fishnet
|
||||
val stockfish: Request.Engine
|
||||
|
||||
def instance(ip: Client.IpAddress) = Client.Instance(
|
||||
def instance(ip: IpAddress) = Client.Instance(
|
||||
fishnet.version,
|
||||
fishnet.python | Client.Python(""),
|
||||
Client.Engines(
|
||||
|
|
|
@ -3,7 +3,8 @@ package lila.fishnet
|
|||
import scala.concurrent.duration._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.dsl.Coll
|
||||
import lila.common.IpAddress
|
||||
import lila.db.dsl._
|
||||
|
||||
private final class Limiter(
|
||||
analysisColl: Coll,
|
||||
|
@ -16,7 +17,7 @@ private final class Limiter(
|
|||
case true => perDayCheck(sender)
|
||||
}
|
||||
|
||||
private val RequestLimitPerIP = new lila.memo.RateLimit(
|
||||
private val RequestLimitPerIP = new lila.memo.RateLimit[IpAddress](
|
||||
credits = 50,
|
||||
duration = 20 hours,
|
||||
name = "request analysis per IP",
|
||||
|
@ -25,12 +26,12 @@ private final class Limiter(
|
|||
|
||||
private def concurrentCheck(sender: Work.Sender) = sender match {
|
||||
case Work.Sender(_, _, mod, system) if (mod || system) => fuccess(true)
|
||||
case Work.Sender(Some(userId), _, _, _) => analysisColl.count(BSONDocument(
|
||||
case Work.Sender(Some(userId), _, _, _) => !analysisColl.exists($doc(
|
||||
"sender.userId" -> userId
|
||||
).some) map (0 ==)
|
||||
case Work.Sender(_, Some(ip), _, _) => analysisColl.count(BSONDocument(
|
||||
))
|
||||
case Work.Sender(_, Some(ip), _, _) => !analysisColl.exists($doc(
|
||||
"sender.ip" -> ip
|
||||
).some) map (0 ==)
|
||||
))
|
||||
case _ => fuccess(false)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.joda.time.DateTime
|
|||
|
||||
import chess.format.{ Uci, FEN }
|
||||
import chess.variant.Variant
|
||||
import lila.common.IpAddress
|
||||
|
||||
sealed trait Work {
|
||||
def _id: Work.Id
|
||||
|
@ -56,12 +57,14 @@ object Work {
|
|||
|
||||
case class Sender(
|
||||
userId: Option[String],
|
||||
ip: Option[String],
|
||||
ip: Option[IpAddress],
|
||||
mod: Boolean,
|
||||
system: Boolean
|
||||
) {
|
||||
|
||||
override def toString = if (system) "lichess" else userId orElse ip getOrElse "unknown"
|
||||
override def toString =
|
||||
if (system) "lichess"
|
||||
else userId orElse ip.map(_.value) getOrElse "unknown"
|
||||
}
|
||||
|
||||
case class Move(
|
||||
|
|
|
@ -214,14 +214,13 @@ object GameRepo {
|
|||
|
||||
def setHoldAlert(pov: Pov, mean: Int, sd: Int, ply: Option[Int] = None) = {
|
||||
import Player.holdAlertBSONHandler
|
||||
coll.update(
|
||||
coll.updateField(
|
||||
$id(pov.gameId),
|
||||
$set(
|
||||
s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" ->
|
||||
Player.HoldAlert(ply = ply | pov.game.turns, mean = mean, sd = sd)
|
||||
)
|
||||
s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}",
|
||||
Player.HoldAlert(ply = ply | pov.game.turns, mean = mean, sd = sd)
|
||||
).void
|
||||
}
|
||||
|
||||
def setBorderAlert(pov: Pov) = setHoldAlert(pov, 0, 0, 20.some)
|
||||
|
||||
def finish(
|
||||
|
|
|
@ -20,7 +20,7 @@ private[lobby] final class SocketHandler(
|
|||
blocking: String => Fu[Set[String]]
|
||||
) {
|
||||
|
||||
private val HookPoolLimitPerMember = new lila.memo.RateLimit(
|
||||
private val HookPoolLimitPerMember = new lila.memo.RateLimit[String](
|
||||
credits = 25,
|
||||
duration = 1 minute,
|
||||
name = "lobby hook/pool per member",
|
||||
|
|
|
@ -8,7 +8,7 @@ import scala.concurrent.duration.Duration
|
|||
/**
|
||||
* side effect throttler that allows X ops per Y unit of time
|
||||
*/
|
||||
final class RateLimit(
|
||||
final class RateLimit[K](
|
||||
credits: Int,
|
||||
duration: Duration,
|
||||
name: String,
|
||||
|
@ -20,7 +20,7 @@ final class RateLimit(
|
|||
|
||||
private val storage = Scaffeine()
|
||||
.expireAfterWrite(duration)
|
||||
.build[String, (Cost, ClearAt)]()
|
||||
.build[K, (Cost, ClearAt)]()
|
||||
|
||||
private def makeClearAt = nowMillis + duration.toMillis
|
||||
|
||||
|
@ -29,7 +29,7 @@ final class RateLimit(
|
|||
|
||||
logger.info(s"[start] $name ($credits/$duration)")
|
||||
|
||||
def apply[A](k: String, cost: Cost = 1, msg: => String = "")(op: => A)(implicit default: Zero[A]): A =
|
||||
def apply[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(implicit default: Zero[A]): A =
|
||||
storage getIfPresent k match {
|
||||
case None =>
|
||||
storage.put(k, cost -> makeClearAt)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package lila.mod
|
||||
|
||||
import lila.common.IpAddress
|
||||
import lila.security.Permission
|
||||
import lila.security.{ Firewall, UserSpy, Store => SecurityStore }
|
||||
import lila.user.{ User, UserRepo, LightUserApi }
|
||||
|
@ -79,8 +80,8 @@ final class ModApi(
|
|||
UserRepo.toggleIpBan(user.id) zip
|
||||
logApi.ban(mod, user.id, !user.ipBan) zip
|
||||
user.ipBan.fold(
|
||||
firewall unblockIps spy.ipStrings,
|
||||
(spy.ipStrings map firewall.blockIp).sequenceFu >>
|
||||
firewall unblockIps spy.ipStrings.map(IpAddress.apply),
|
||||
(spy.ipStrings map { i => firewall blockIp IpAddress(i) }).sequenceFu >>
|
||||
(SecurityStore disconnect user.id)
|
||||
) void
|
||||
}
|
||||
|
@ -116,7 +117,7 @@ final class ModApi(
|
|||
}
|
||||
|
||||
def ipban(mod: String, ip: String): Funit =
|
||||
(firewall blockIp ip) >> logApi.ipban(mod, ip)
|
||||
(firewall blockIp IpAddress(ip)) >> logApi.ipban(mod, ip)
|
||||
|
||||
private def withUser[A](username: String)(op: User => Fu[A]): Fu[A] =
|
||||
UserRepo named username flatten "[mod] missing user " + username flatMap op
|
||||
|
|
|
@ -122,12 +122,15 @@ final class Env(
|
|||
actor
|
||||
}
|
||||
|
||||
lazy val selfReport = new SelfReport(roundMap)
|
||||
|
||||
lazy val socketHandler = new SocketHandler(
|
||||
hub = hub,
|
||||
roundMap = roundMap,
|
||||
socketHub = socketHub,
|
||||
messenger = messenger,
|
||||
evalCacheHandler = evalCacheHandler,
|
||||
selfReport = selfReport,
|
||||
bus = system.lilaBus
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package lila.round
|
||||
|
||||
import akka.actor._
|
||||
import play.api.mvc.RequestHeader
|
||||
|
||||
import lila.common.{ IpAddress, HTTPRequest }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class SelfReport(roundMap: ActorRef) {
|
||||
|
||||
def apply(
|
||||
userId: Option[User.ID],
|
||||
ip: IpAddress,
|
||||
fullId: String,
|
||||
name: String
|
||||
): Funit =
|
||||
userId.??(UserRepo.named) flatMap { user =>
|
||||
val known = user.??(_.engine)
|
||||
lila.mon.cheat.cssBot()
|
||||
// user.ifTrue(!known && name != "ceval") ?? { u =>
|
||||
// Env.report.api.autoBotReport(u.id, referer, name)
|
||||
// }
|
||||
def doLog = lila.log("cheat").branch("jslog").info(
|
||||
s"$ip https://lichess.org/$fullId ${user.fold("anon")(_.id)} $name"
|
||||
)
|
||||
if (fullId == "________") fuccess(doLog)
|
||||
else lila.game.GameRepo pov fullId map {
|
||||
_ ?? { pov =>
|
||||
if (!known) doLog
|
||||
if (Set("ceval", "rcb", "ccs")(name)) fuccess {
|
||||
roundMap ! lila.hub.actorApi.map.Tell(
|
||||
pov.gameId,
|
||||
lila.round.actorApi.round.Cheat(pov.color)
|
||||
)
|
||||
}
|
||||
else lila.game.GameRepo.setBorderAlert(pov)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@ import chess.format.Uci
|
|||
import play.api.libs.json.{ JsObject, Json }
|
||||
|
||||
import actorApi._, round._
|
||||
import lila.common.ApiVersion
|
||||
import lila.common.PimpedJson._
|
||||
import lila.common.{ IpAddress, ApiVersion }
|
||||
import lila.game.{ Pov, PovRef, GameRepo }
|
||||
import lila.hub.actorApi.map._
|
||||
import lila.hub.actorApi.round.Berserk
|
||||
|
@ -26,6 +26,7 @@ private[round] final class SocketHandler(
|
|||
hub: lila.hub.Env,
|
||||
messenger: Messenger,
|
||||
evalCacheHandler: lila.evalCache.EvalCacheSocketHandler,
|
||||
selfReport: SelfReport,
|
||||
bus: lila.common.Bus
|
||||
) {
|
||||
|
||||
|
@ -97,6 +98,10 @@ private[round] final class SocketHandler(
|
|||
hub.actor.tournamentApi ! Berserk(gameId, userId)
|
||||
member push ackEvent
|
||||
}
|
||||
case ("rep", o) => for {
|
||||
d ← o obj "d"
|
||||
name ← d str "n"
|
||||
} selfReport(member.userId, member.ip, s"$gameId$playerId", name)
|
||||
}: Handler.Controller) orElse lila.chat.Socket.in(
|
||||
chatId = gameId,
|
||||
member = member,
|
||||
|
@ -111,7 +116,7 @@ private[round] final class SocketHandler(
|
|||
colorName: String,
|
||||
uid: Uid,
|
||||
user: Option[User],
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
userTv: Option[String],
|
||||
apiVersion: ApiVersion
|
||||
): Fu[Option[JsSocketHandler]] =
|
||||
|
@ -123,7 +128,7 @@ private[round] final class SocketHandler(
|
|||
pov: Pov,
|
||||
uid: Uid,
|
||||
user: Option[User],
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
apiVersion: ApiVersion
|
||||
): Fu[JsSocketHandler] =
|
||||
join(pov, Some(pov.playerId), uid, user, ip, userTv = none, apiVersion)
|
||||
|
@ -133,7 +138,7 @@ private[round] final class SocketHandler(
|
|||
playerId: Option[String],
|
||||
uid: Uid,
|
||||
user: Option[User],
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
userTv: Option[String],
|
||||
apiVersion: ApiVersion
|
||||
): Fu[JsSocketHandler] = {
|
||||
|
|
|
@ -7,7 +7,7 @@ import scala.concurrent.Promise
|
|||
import chess.Color
|
||||
import chess.format.Uci
|
||||
|
||||
import lila.common.ApiVersion
|
||||
import lila.common.{ IpAddress, ApiVersion }
|
||||
import lila.game.Event
|
||||
import lila.socket.SocketMember
|
||||
import lila.socket.Socket.Uid
|
||||
|
@ -20,7 +20,7 @@ sealed trait Member extends SocketMember {
|
|||
val color: Color
|
||||
val playerIdOption: Option[String]
|
||||
val troll: Boolean
|
||||
val ip: String
|
||||
val ip: IpAddress
|
||||
val userTv: Option[String]
|
||||
val apiVersion: ApiVersion
|
||||
|
||||
|
@ -36,7 +36,7 @@ object Member {
|
|||
user: Option[User],
|
||||
color: Color,
|
||||
playerIdOption: Option[String],
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
userTv: Option[String],
|
||||
apiVersion: ApiVersion
|
||||
): Member = {
|
||||
|
@ -54,7 +54,7 @@ case class Owner(
|
|||
playerId: String,
|
||||
color: Color,
|
||||
troll: Boolean,
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
apiVersion: ApiVersion
|
||||
) extends Member {
|
||||
|
||||
|
@ -67,7 +67,7 @@ case class Watcher(
|
|||
userId: Option[String],
|
||||
color: Color,
|
||||
troll: Boolean,
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
userTv: Option[String],
|
||||
apiVersion: ApiVersion
|
||||
) extends Member {
|
||||
|
@ -80,7 +80,7 @@ case class Join(
|
|||
user: Option[User],
|
||||
color: Color,
|
||||
playerId: Option[String],
|
||||
ip: String,
|
||||
ip: IpAddress,
|
||||
userTv: Option[String],
|
||||
apiVersion: ApiVersion
|
||||
)
|
||||
|
@ -138,7 +138,7 @@ package round {
|
|||
case object Abandon
|
||||
case class ForecastPlay(lastMove: chess.Move)
|
||||
case class Cheat(color: Color)
|
||||
case class HoldAlert(playerId: String, mean: Int, sd: Int, ip: String)
|
||||
case class HoldAlert(playerId: String, mean: Int, sd: Int, ip: IpAddress)
|
||||
case class GoBerserk(color: Color)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import play.api.data.Forms._
|
|||
import play.api.mvc.RequestHeader
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.common.ApiVersion
|
||||
import lila.common.{ ApiVersion, IpAddress }
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
@ -92,7 +92,7 @@ final class Api(
|
|||
|
||||
def userIdsSharingFingerprint = userIdsSharingField("fp") _
|
||||
|
||||
def recentByIpExists(ip: String): Fu[Boolean] = Store recentByIpExists ip
|
||||
def recentByIpExists(ip: IpAddress): Fu[Boolean] = Store recentByIpExists ip
|
||||
|
||||
private def userIdsSharingField(field: String)(userId: String): Fu[List[String]] =
|
||||
coll.distinct[String, List](
|
||||
|
|
|
@ -8,7 +8,7 @@ import ornicar.scalalib.Random
|
|||
import play.api.mvc.Results.Redirect
|
||||
import play.api.mvc.{ RequestHeader, Action, Cookies }
|
||||
|
||||
import lila.common.LilaCookie
|
||||
import lila.common.{ IpAddress, LilaCookie }
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
|
||||
|
@ -34,14 +34,18 @@ final class Firewall(
|
|||
|
||||
def accepts(req: RequestHeader): Fu[Boolean] = blocks(req) map (!_)
|
||||
|
||||
def blockIp(ip: String): Funit = validIp(ip) ?? {
|
||||
coll.update($id(ip), $doc("_id" -> ip, "date" -> DateTime.now), upsert = true).void >>- refresh
|
||||
def blockIp(ip: IpAddress): Funit = validIp(ip) ?? {
|
||||
coll.update(
|
||||
$id(ip),
|
||||
$doc("_id" -> ip, "date" -> DateTime.now),
|
||||
upsert = true
|
||||
).void >>- refresh
|
||||
}
|
||||
|
||||
def unblockIps(ips: Iterable[String]): Funit =
|
||||
coll.remove($inIds(ips filter validIp)).void >>- refresh
|
||||
def unblockIps(ips: Iterable[IpAddress]): Funit =
|
||||
coll.remove($inIds(ips.filter(validIp).map(_.value))).void >>- refresh
|
||||
|
||||
def blocksIp(ip: String): Fu[Boolean] = ips contains ip
|
||||
def blocksIp(ip: IpAddress): Fu[Boolean] = ips contains ip.value
|
||||
|
||||
private def refresh {
|
||||
ips.clear
|
||||
|
@ -50,15 +54,9 @@ final class Firewall(
|
|||
private def blocksCookies(cookies: Cookies, name: String) =
|
||||
(cookies get name).isDefined
|
||||
|
||||
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
|
||||
private val ipv4Regex = """^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
|
||||
|
||||
// ipv6 address in standard form (no compression, no leading zeros)
|
||||
private val ipv6Regex = """^((0|[1-9a-f][0-9a-f]{0,3}):){7}(0|[1-9a-f][0-9a-f]{0,3})""".r
|
||||
|
||||
private def validIp(ip: String) =
|
||||
((ipv4Regex matches ip) && ip != "127.0.0.1" && ip != "0.0.0.0") ||
|
||||
((ipv6Regex matches ip) && ip != "0:0:0:0:0:0:0:1" && ip != "0:0:0:0:0:0:0:0")
|
||||
private def validIp(ip: IpAddress) =
|
||||
(IpAddress.isv4(ip) && ip.value != "127.0.0.1" && ip.value != "0.0.0.0") ||
|
||||
(IpAddress.isv6(ip) && ip.value != "0:0:0:0:0:0:0:1" && ip.value != "0:0:0:0:0:0:0:0")
|
||||
|
||||
private type IP = Vector[Byte]
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.joda.time.DateTime
|
|||
import play.api.mvc.RequestHeader
|
||||
import reactivemongo.bson.Macros
|
||||
|
||||
import lila.common.{ HTTPRequest, ApiVersion }
|
||||
import lila.common.{ HTTPRequest, ApiVersion, IpAddress }
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
|
||||
|
@ -22,7 +22,7 @@ object Store {
|
|||
coll.insert($doc(
|
||||
"_id" -> sessionId,
|
||||
"user" -> userId,
|
||||
"ip" -> HTTPRequest.lastRemoteAddress(req),
|
||||
"ip" -> HTTPRequest.lastRemoteAddress(req).value,
|
||||
"ua" -> HTTPRequest.userAgent(req).|("?"),
|
||||
"date" -> DateTime.now,
|
||||
"up" -> true,
|
||||
|
@ -132,6 +132,6 @@ object Store {
|
|||
coll.remove($inIds(olds.map(_._id))).void
|
||||
}
|
||||
|
||||
private[security] def recentByIpExists(ip: String): Fu[Boolean] =
|
||||
private[security] def recentByIpExists(ip: IpAddress): Fu[Boolean] =
|
||||
coll.exists($doc("ip" -> ip, "date" -> $gt(DateTime.now minusDays 7)))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package lila.security
|
||||
|
||||
import lila.common.IpAddress
|
||||
|
||||
import play.api.libs.ws.WS
|
||||
import play.api.Play.current
|
||||
|
||||
|
@ -7,10 +9,10 @@ final class Tor(providerUrl: String) {
|
|||
|
||||
private var ips = Set[String]()
|
||||
|
||||
private[security] def refresh(withIps: Iterable[String] => Funit) {
|
||||
private[security] def refresh(withIps: Iterable[IpAddress] => Funit) {
|
||||
WS.url(providerUrl).get() map { res =>
|
||||
ips = res.body.lines.filterNot(_ startsWith "#").toSet
|
||||
withIps(ips)
|
||||
withIps(ips map IpAddress.apply)
|
||||
lila.mon.security.tor.node(ips.size)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package lila.security
|
||||
|
||||
import lila.common.IpAddress
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -40,7 +41,9 @@ object UserSpy {
|
|||
user ← UserRepo named userId flatten "[spy] user not found"
|
||||
infos ← Store.findInfoByUser(user.id)
|
||||
ips = infos.map(_.ip).distinct
|
||||
blockedIps ← (ips map firewall.blocksIp).sequenceFu
|
||||
blockedIps ← (ips map { i =>
|
||||
firewall blocksIp IpAddress(i)
|
||||
}).sequenceFu
|
||||
locations <- scala.concurrent.Future {
|
||||
ips map geoIP.orUnknown
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ object Handler {
|
|||
|
||||
val emptyController: Controller = PartialFunction.empty
|
||||
|
||||
private val AnaRateLimiter = new lila.memo.RateLimit(120, 30 seconds,
|
||||
private val AnaRateLimiter = new lila.memo.RateLimit[String](120, 30 seconds,
|
||||
name = "socket analysis move",
|
||||
key = "socket_analysis_move")
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ private[study] final class SocketHandler(
|
|||
import Handler.AnaRateLimit
|
||||
import JsonView.shapeReader
|
||||
|
||||
private val InviteLimitPerUser = new lila.memo.RateLimit(
|
||||
private val InviteLimitPerUser = new lila.memo.RateLimit[User.ID](
|
||||
credits = 50,
|
||||
duration = 24 hour,
|
||||
name = "study invites per user",
|
||||
|
|
|
@ -22,7 +22,7 @@ sealed trait UserContext {
|
|||
|
||||
def troll = me.??(_.troll)
|
||||
|
||||
def ip = req.remoteAddress
|
||||
def ip = lila.common.HTTPRequest lastRemoteAddress req
|
||||
|
||||
def kid = me.??(_.kid)
|
||||
def noKid = !kid
|
||||
|
|
|
@ -98,4 +98,11 @@ module.exports = function(send, ctrl) {
|
|||
}
|
||||
return false;
|
||||
}.bind(this);
|
||||
|
||||
lichess.pubsub.on('ab.rep', function(n) {
|
||||
console.log(n);
|
||||
send('rep', {
|
||||
n: n
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue