don't always publish user.online to discourage API misuse

Use this instead: https://lichess.org/api#operation/apiUsersStatus
pull/9850/head
Thibault Duplessis 2021-09-20 17:53:59 +02:00
parent be678f9a41
commit ced5e57c93
10 changed files with 34 additions and 41 deletions

View File

@ -74,7 +74,7 @@ final class Account(
env.playban.api.currentBan(me.id) map { case (((prefs, povs), nbChallenges), playban) =>
Ok {
import lila.pref.JsonView._
env.user.jsonView(me) ++ Json
env.user.jsonView(me, withOnline = true) ++ Json
.obj(
"prefs" -> prefs,
"nowPlaying" -> JsArray(povs take 50 map env.api.lobbyApi.nowPlaying),

View File

@ -66,7 +66,7 @@ final class Api(
UsersRateLimitPerIP(ip, cost = cost) {
lila.mon.api.users.increment(cost.toLong)
env.user.repo enabledNameds usernames map {
_.map { env.user.jsonView(_, none) }
_.map { env.user.jsonView(_, none, withOnline = false) }
} map toApiResult map toHttp
}(rateLimitedFu)
}

View File

@ -28,7 +28,7 @@ final class Auth(
private def mobileUserOk(u: UserModel, sessionId: String): Fu[Result] =
env.round.proxyRepo urgentGames u map { povs =>
Ok {
env.user.jsonView(u) ++ Json.obj(
env.user.jsonView(u, withOnline = true) ++ Json.obj(
"nowPlaying" -> JsArray(povs take 20 map env.api.lobbyApi.nowPlaying),
"sessionId" -> sessionId
)

View File

@ -184,7 +184,7 @@ final class PlayApi(
.botsByIdsCursor(env.bot.onlineApiUsers.get)
.documentSource(getInt("nb", req) | Int.MaxValue)
.throttle(50, 1 second)
.map { env.user.jsonView(_) }
.map { env.user.jsonView(_, withOnline = false) }
}
}
}

View File

@ -142,18 +142,13 @@ final class Relation(
)
}
def apiFollowing(name: String) =
Action.async { implicit req =>
env.user.repo.enabledNamed(name) flatMap {
_ ?? { user =>
apiC.jsonStream {
env.relation.stream
.follow(user, Direction.Following, MaxPerSecond(20))
.map(env.api.userApi.one)
}.fuccess
}
}
}
def apiFollowing = Scoped() { implicit req => me =>
apiC.jsonStream {
env.relation.stream
.follow(me, Direction.Following, MaxPerSecond(30))
.map(env.api.userApi.one(_, withOnline = false))
}.fuccess
}
private def jsonRelatedPaginator(pag: Paginator[Related]) = {
import lila.user.JsonView.nameWrites

View File

@ -112,7 +112,7 @@ final class Team(
apiC.jsonStream(
env.team
.memberStream(team, MaxPerSecond(20))
.map(env.api.userApi.one)
.map(env.api.userApi.one(_, withOnline = false))
)(req)
case false => Unauthorized
}

View File

@ -207,7 +207,7 @@ final class User(
Json.toJson(
users
.take(getInt("nb", req).fold(10)(_ min max))
.map(env.user.jsonView(_))
.map(env.user.jsonView(_, withOnline = false))
)
)
}

View File

@ -20,11 +20,8 @@ final private[api] class UserApi(
net: NetConfig
)(implicit ec: scala.concurrent.ExecutionContext) {
def pagerJson(pag: Paginator[User]): JsObject =
Json.obj("paginator" -> PaginatorJson(pag mapResults one))
def one(u: User): JsObject =
addPlayingStreaming(jsonView(u), u.id) ++
def one(u: User, withOnline: Boolean): JsObject =
addStreaming(jsonView(u, withOnline = withOnline), u.id) ++
Json.obj("url" -> makeUrl(s"@/${u.username}")) // for app BC
def extended(username: String, as: Option[User], withFollows: Boolean): Fu[Option[JsObject]] =
@ -62,7 +59,7 @@ final private[api] class UserApi(
case ((((((((((gameOption,nbGamesWithMe),following),followers),followable),
relation),isFollowed),nbBookmarks),nbPlaying),nbImported),completionRate)=>
// format: on
jsonView(u) ++ {
jsonView(u, withOnline = true) ++ {
Json
.obj(
"url" -> makeUrl(s"@/${u.username}"), // for app BC
@ -99,7 +96,7 @@ final private[api] class UserApi(
}
}
private def addPlayingStreaming(js: JsObject, id: User.ID) =
private def addStreaming(js: JsObject, id: User.ID) =
js.add("streaming", liveStreamApi.isStreaming(id))
private def makeUrl(path: String): String = s"${net.baseUrl}/$path"

View File

@ -13,10 +13,10 @@ case class Crosstable(
def nonEmpty = results.nonEmpty option this
def nbGames = users.nbGames
def showScore = users.showScore _
def showOpponentScore = users.showOpponentScore _
def fromPov(userId: String) = copy(users = users fromPov userId)
def nbGames = users.nbGames
def showScore = users.showScore _
def showOpponentScore = users.showOpponentScore _
def fromPov(userId: lila.user.User.ID) = copy(users = users fromPov userId)
lazy val size = results.size
@ -33,19 +33,19 @@ object Crosstable {
Nil
)
case class User(id: String, score: Int) // score is x10
case class User(id: lila.user.User.ID, score: Int) // score is x10
case class Users(user1: User, user2: User) {
val nbGames = (user1.score + user2.score) / 10
def user(id: String): Option[User] =
def user(id: lila.user.User.ID): Option[User] =
if (id == user1.id) Some(user1)
else if (id == user2.id) Some(user2)
else None
def toList = List(user1, user2)
def showScore(userId: String) = {
def showScore(userId: lila.user.User.ID) = {
val byTen = user(userId) ?? (_.score)
s"${byTen / 10}${(byTen % 10 != 0).??("½")}" match {
case "0½" => "½"
@ -53,12 +53,12 @@ object Crosstable {
}
}
def showOpponentScore(userId: String) =
def showOpponentScore(userId: lila.user.User.ID) =
if (userId == user1.id) showScore(user2.id).some
else if (userId == user2.id) showScore(user1.id).some
else none
def fromPov(userId: String) =
def fromPov(userId: lila.user.User.ID) =
if (userId == user2.id) copy(user1 = user2, user2 = user1)
else this
@ -68,22 +68,23 @@ object Crosstable {
else None
}
case class Result(gameId: Game.ID, winnerId: Option[String])
case class Result(gameId: Game.ID, winnerId: Option[lila.user.User.ID])
case class Matchup(users: Users) { // score is x10
def fromPov(userId: String) = copy(users = users fromPov userId)
def nonEmpty = users.nbGames > 0
def fromPov(userId: lila.user.User.ID) = copy(users = users fromPov userId)
def nonEmpty = users.nbGames > 0
}
case class WithMatchup(crosstable: Crosstable, matchup: Option[Matchup]) {
def fromPov(userId: String) =
def fromPov(userId: lila.user.User.ID) =
copy(
crosstable fromPov userId,
matchup map (_ fromPov userId)
)
}
private[game] def makeKey(u1: String, u2: String): String = if (u1 < u2) s"$u1/$u2" else s"$u2/$u1"
private[game] def makeKey(u1: lila.user.User.ID, u2: lila.user.User.ID): String =
if (u1 < u2) s"$u1/$u2" else s"$u2/$u1"
import reactivemongo.api.bson._
import lila.db.BSON

View File

@ -12,15 +12,15 @@ final class JsonView(isOnline: lila.socket.IsOnline) {
implicit private val profileWrites = Json.writes[Profile]
implicit private val playTimeWrites = Json.writes[PlayTime]
def apply(u: User, onlyPerf: Option[PerfType] = None): JsObject =
def apply(u: User, onlyPerf: Option[PerfType] = None, withOnline: Boolean): JsObject =
Json
.obj(
"id" -> u.id,
"username" -> u.username,
"online" -> isOnline(u.id),
"perfs" -> perfs(u, onlyPerf),
"createdAt" -> u.createdAt
)
.add("online" -> withOnline.option(isOnline(u.id)))
.add("disabled" -> u.disabled)
.add("tosViolation" -> u.lame)
.add("profile" -> u.profile.map(p => profileWrites.writes(p.filterTroll(u.marks.troll)).noNull))