introduce lila.common.Bearer newtype

pull/9343/head
Niklas Fiekas 2021-07-03 16:27:40 +02:00
parent a32038828c
commit 0327f65be9
13 changed files with 46 additions and 49 deletions

View File

@ -51,7 +51,7 @@ final class BulkPairing(env: Env) extends LilaController(env) {
Json.obj(
"tokens" -> JsObject {
tokens.map { case BadToken(token, error) =>
token.value -> JsString(error.message)
token.secret -> JsString(error.message)
}
}
)

View File

@ -9,13 +9,12 @@ import views.html
import lila.api.Context
import lila.app._
import lila.challenge.{ Challenge => ChallengeModel }
import lila.common.{ HTTPRequest, IpAddress }
import lila.common.{ Bearer, HTTPRequest, IpAddress, Template }
import lila.game.{ AnonCookie, Pov }
import lila.oauth.{ AccessToken, OAuthScope }
import lila.setup.ApiConfig
import lila.socket.Socket.SocketVersion
import lila.user.{ User => UserModel }
import lila.common.Template
final class Challenge(
env: Env
@ -202,7 +201,7 @@ final class Challenge(
Action.async { req =>
import cats.implicits._
val scopes = List(OAuthScope.Challenge.Write)
(get("token1", req) map AccessToken.Id.apply, get("token2", req) map AccessToken.Id.apply).mapN {
(get("token1", req) map Bearer.apply, get("token2", req) map Bearer.apply).mapN {
env.oAuth.server.authBoth(scopes)
} ?? {
_ flatMap {
@ -373,7 +372,7 @@ final class Challenge(
strToken: String
)(managedBy: lila.user.User, message: Option[Template]) =
env.security.api.oauthScoped(
lila.oauth.AccessToken.Id(strToken),
Bearer(strToken),
List(lila.oauth.OAuthScope.Challenge.Write)
) flatMap {
_.fold(

View File

@ -84,7 +84,7 @@ final class OAuth(env: Env) extends LilaController(env) {
Json
.obj(
"token_type" -> "Bearer",
"access_token" -> token.id.value
"access_token" -> token.id.secret
)
.add("expires_in" -> token.expires.map(_.getSeconds - nowSeconds))
)
@ -106,7 +106,7 @@ final class OAuth(env: Env) extends LilaController(env) {
Json
.obj(
"token_type" -> "Bearer",
"access_token" -> token.id.value,
"access_token" -> token.id.secret,
"refresh_token" -> s"invalid_for_bc_${lila.common.ThreadLocalRandom.nextString(17)}"
)
.add("expires_in" -> token.expires.map(_.getSeconds - nowSeconds))
@ -121,7 +121,7 @@ final class OAuth(env: Env) extends LilaController(env) {
def tokenRevoke =
Scoped() { implicit req => _ =>
HTTPRequest.bearer(req) ?? { token =>
env.oAuth.tokenApi.revoke(AccessToken.Id(token)) map env.oAuth.server.deleteCached inject NoContent
env.oAuth.tokenApi.revoke(token) map env.oAuth.server.deleteCached inject NoContent
}
}

View File

@ -69,7 +69,7 @@ object dgt {
)
def play(token: AccessToken)(implicit ctx: Context) =
layout("play", embedJsUnsafeLoadThen(s"""LichessDgt.playPage("${token.id.value}")"""))(
layout("play", embedJsUnsafeLoadThen(s"""LichessDgt.playPage("${token.id.secret}")"""))(
div(id := "dgt-play-zone")(pre(id := "dgt-play-zone-log")),
div(cls := "dgt__play__help")(
h2(iconTag("", "If a move is not detected")),

View File

@ -54,7 +54,7 @@ object index {
br,
"You wont be able to see it again!"
),
code(token.id.value)
code(token.id.secret)
)
)
},

View File

@ -91,10 +91,10 @@ object HTTPRequest {
def printClient(req: RequestHeader) =
s"${ipAddress(req)} origin:${~origin(req)} referer:${~referer(req)} ua:${~userAgent(req)}"
def bearer(req: RequestHeader): Option[String] =
def bearer(req: RequestHeader): Option[Bearer] =
req.headers.get(HeaderNames.AUTHORIZATION).flatMap { authorization =>
val prefix = "Bearer "
authorization.startsWith(prefix) option authorization.stripPrefix(prefix)
authorization.startsWith(prefix) option Bearer(authorization.stripPrefix(prefix))
}
def isOAuth(req: RequestHeader) = bearer(req).isDefined

View File

@ -20,6 +20,14 @@ object AssetVersion {
case class IsMobile(value: Boolean) extends AnyVal with BooleanValue
case class Bearer(secret: String) extends AnyVal {
override def toString = "Bearer(***)"
}
object Bearer {
def random() = Bearer(s"lio_${SecureRandom.nextString(32)}")
def randomPersonal() = Bearer(SecureRandom.nextString(16)) // TODO: prefix lip_, more entropy
}
sealed trait IpAddress {
def value: String
override def toString = value

View File

@ -3,11 +3,11 @@ package lila.oauth
import org.joda.time.DateTime
import reactivemongo.api.bson._
import lila.common.SecureRandom
import lila.common.{ Bearer, SecureRandom }
import lila.user.User
case class AccessToken(
id: AccessToken.Id,
id: Bearer,
publicId: BSONObjectID,
userId: User.ID,
createdAt: Option[DateTime] = None, // for personal access tokens
@ -22,12 +22,6 @@ case class AccessToken(
object AccessToken {
case class Id(value: String) extends AnyVal
object Id {
def random() = Id(s"lio_${SecureRandom.nextString(32)}")
def randomPersonal() = Id(SecureRandom.nextString(16)) // TODO: prefix lip_, more entropy
}
case class ForAuth(userId: User.ID, scopes: List[OAuthScope])
object BSONFields {
@ -52,7 +46,7 @@ object AccessToken {
BSONFields.scopes -> true
)
implicit private[oauth] val accessTokenIdHandler = stringAnyValHandler[Id](_.value, Id.apply)
implicit private[oauth] val accessTokenIdHandler = stringAnyValHandler[Bearer](_.secret, Bearer.apply)
implicit val ForAuthBSONReader = new BSONDocumentReader[ForAuth] {
def readDocument(doc: BSONDocument) =
@ -68,7 +62,7 @@ object AccessToken {
def reads(r: BSON.Reader): AccessToken =
AccessToken(
id = r.get[Id](id),
id = r.get[Bearer](id),
publicId = r.get[BSONObjectID](publicId),
userId = r str userId,
createdAt = r.getO[DateTime](createdAt),

View File

@ -3,6 +3,7 @@ package lila.oauth
import org.joda.time.DateTime
import reactivemongo.api.bson._
import lila.common.Bearer
import lila.db.dsl._
import lila.user.User
@ -15,7 +16,7 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
def create(granted: AccessTokenRequest.Granted): Fu[AccessToken] = {
val token = AccessToken(
id = AccessToken.Id.random(),
id = Bearer.random(),
publicId = BSONObjectID.generate(),
userId = granted.userId,
createdAt = DateTime.now().some,
@ -90,12 +91,12 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
} yield AccessTokenApi.Client(origin, usedAt, scopes)
}
def revoke(token: AccessToken.Id): Fu[AccessToken.Id] =
def revoke(token: Bearer): Fu[Bearer] =
colls.token {
_.delete.one($doc(F.id -> token)).inject(token)
}
def revokeByClientOrigin(clientOrigin: String, user: User): Fu[List[AccessToken.Id]] =
def revokeByClientOrigin(clientOrigin: String, user: User): Fu[List[Bearer]] =
colls.token { coll =>
coll
.find(
@ -116,7 +117,7 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
F.clientOrigin -> clientOrigin
)
)
.inject(invalidate.flatMap(_.getAsOpt[AccessToken.Id](F.id)))
.inject(invalidate.flatMap(_.getAsOpt[Bearer](F.id)))
}
}

View File

@ -6,6 +6,7 @@ import play.api.data._
import play.api.data.Forms._
import reactivemongo.api.bson.BSONObjectID
import lila.common.Bearer
import lila.common.Form.absoluteUrl
object OAuthForm {
@ -29,7 +30,7 @@ object OAuthForm {
) {
def make(user: lila.user.User) =
AccessToken(
id = AccessToken.Id.randomPersonal(),
id = Bearer.randomPersonal(),
publicId = BSONObjectID.generate(),
userId = user.id,
createdAt = DateTime.now.some,

View File

@ -4,7 +4,7 @@ import org.joda.time.DateTime
import play.api.mvc.{ RequestHeader, Result }
import scala.concurrent.duration._
import lila.common.HTTPRequest
import lila.common.{ Bearer, HTTPRequest }
import lila.db.dsl._
import lila.user.{ User, UserRepo }
@ -19,13 +19,13 @@ final class OAuthServer(
import OAuthServer._
def auth(req: RequestHeader, scopes: List[OAuthScope]): Fu[AuthResult] =
reqToTokenId(req).fold[Fu[AuthResult]](fufail(MissingAuthorizationHeader)) {
HTTPRequest.bearer(req).fold[Fu[AuthResult]](fufail(MissingAuthorizationHeader)) {
auth(_, scopes)
} recover { case e: AuthError =>
Left(e)
}
def auth(tokenId: AccessToken.Id, scopes: List[OAuthScope]): Fu[AuthResult] =
def auth(tokenId: Bearer, scopes: List[OAuthScope]): Fu[AuthResult] =
accessTokenCache.get(tokenId) orFailWith NoSuchToken flatMap {
case at if scopes.nonEmpty && !scopes.exists(at.scopes.contains) => fufail(MissingScope(at.scopes))
case at =>
@ -38,8 +38,8 @@ final class OAuthServer(
}
def authBoth(scopes: List[OAuthScope])(
token1: AccessToken.Id,
token2: AccessToken.Id
token1: Bearer,
token2: Bearer
): Fu[Either[AuthError, (User, User)]] = for {
auth1 <- auth(token1, scopes)
auth2 <- auth(token2, scopes)
@ -49,19 +49,16 @@ final class OAuthServer(
result <- if (user1.user is user2.user) Left(OneUserWithTwoTokens) else Right(user1.user -> user2.user)
} yield result
def deleteCached(id: AccessToken.Id): Unit =
def deleteCached(id: Bearer): Unit =
accessTokenCache.put(id, fuccess(none))
private def reqToTokenId(req: RequestHeader): Option[AccessToken.Id] =
HTTPRequest.bearer(req).map(AccessToken.Id.apply)
private val accessTokenCache =
cacheApi[AccessToken.Id, Option[AccessToken.ForAuth]](32, "oauth.server.personal_access_token") {
cacheApi[Bearer, Option[AccessToken.ForAuth]](32, "oauth.server.personal_access_token") {
_.expireAfterWrite(5 minutes)
.buildAsyncFuture(fetchAccessToken)
}
private def fetchAccessToken(tokenId: AccessToken.Id): Fu[Option[AccessToken.ForAuth]] =
private def fetchAccessToken(tokenId: Bearer): Fu[Option[AccessToken.ForAuth]] =
colls.token {
_.ext.findAndUpdate[AccessToken.ForAuth](
selector = $doc(F.id -> tokenId),

View File

@ -11,7 +11,7 @@ import reactivemongo.api.ReadPreference
import scala.annotation.nowarn
import scala.concurrent.duration._
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest, IpAddress, SecureRandom }
import lila.common.{ ApiVersion, Bearer, EmailAddress, HTTPRequest, IpAddress, SecureRandom }
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.dsl._
import lila.oauth.{ AccessToken, OAuthScope, OAuthServer }
@ -147,7 +147,7 @@ final class SecurityApi(
private def stripRolesOfUser(user: User) = user.copy(roles = user.roles.filter(nonModRoles.contains))
def oauthScoped(
tokenId: AccessToken.Id,
tokenId: Bearer,
scopes: List[lila.oauth.OAuthScope]
): Fu[lila.oauth.OAuthServer.AuthResult] =
tryOauthServer().flatMap {

View File

@ -9,13 +9,10 @@ import play.api.data.Forms._
import play.api.libs.json.Json
import scala.concurrent.duration._
import lila.game.Game
import lila.game.IdGenerator
import lila.oauth.AccessToken
import lila.oauth.OAuthScope
import lila.oauth.OAuthServer
import lila.common.{ Bearer, Template }
import lila.game.{ Game, IdGenerator }
import lila.oauth.{ AccessToken, OAuthScope, OAuthServer }
import lila.user.User
import lila.common.Template
object SetupBulk {
@ -76,7 +73,7 @@ object SetupBulk {
}(_ => None)
)
private[setup] def extractTokenPairs(str: String): List[(AccessToken.Id, AccessToken.Id)] =
private[setup] def extractTokenPairs(str: String): List[(Bearer, Bearer)] =
str
.split(',')
.view
@ -85,11 +82,11 @@ object SetupBulk {
w.trim -> b.trim
}
.collect {
case (w, b) if w.nonEmpty && b.nonEmpty => (AccessToken.Id(w), AccessToken.Id(b))
case (w, b) if w.nonEmpty && b.nonEmpty => (Bearer(w), Bearer(b))
}
.toList
case class BadToken(token: AccessToken.Id, error: OAuthServer.AuthError)
case class BadToken(token: Bearer, error: OAuthServer.AuthError)
case class ScheduledGame(id: Game.ID, white: User.ID, black: User.ID)