remove oauth app crud
parent
5d2f68911f
commit
86969b97e2
|
@ -123,7 +123,6 @@ final class LilaComponents(ctx: ApplicationLoader.Context) extends BuiltInCompon
|
|||
lazy val gameMod: GameMod = wire[GameMod]
|
||||
lazy val notifyC: Notify = wire[Notify]
|
||||
lazy val oAuth: OAuth = wire[OAuth]
|
||||
lazy val oAuthApp: OAuthApp = wire[OAuthApp]
|
||||
lazy val oAuthToken: OAuthToken = wire[OAuthToken]
|
||||
lazy val options: Options = wire[Options]
|
||||
lazy val page: Page = wire[Page]
|
||||
|
|
|
@ -11,7 +11,7 @@ import views._
|
|||
import lila.api.Context
|
||||
import lila.common.HTTPRequest
|
||||
import lila.app._
|
||||
import lila.oauth.{ AccessToken, AccessTokenRequest, AuthorizationRequest, PersonalToken }
|
||||
import lila.oauth.{ AccessToken, AccessTokenRequest, AuthorizationRequest }
|
||||
|
||||
final class OAuth(env: Env) extends LilaController(env) {
|
||||
|
||||
|
@ -38,7 +38,7 @@ final class OAuth(env: Env) extends LilaController(env) {
|
|||
withPrompt { prompt =>
|
||||
fuccess(ctx.me.fold(Redirect(routes.Auth.login.url, Map("referrer" -> List(ctx.req.uri)))) { me =>
|
||||
Ok(
|
||||
html.oAuth.app.authorize(prompt, me, s"${routes.OAuth.authorizeApply}?${ctx.req.rawQueryString}")
|
||||
html.oAuth.authorize(prompt, me, s"${routes.OAuth.authorizeApply}?${ctx.req.rawQueryString}")
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import lila.app._
|
||||
import lila.oauth.{ AccessToken, OAuthApp => App }
|
||||
import views._
|
||||
|
||||
final class OAuthApp(env: Env) extends LilaController(env) {
|
||||
|
||||
private val appApi = env.oAuth.appApi
|
||||
private val forms = env.oAuth.forms
|
||||
|
||||
def index =
|
||||
Auth { implicit ctx => me =>
|
||||
appApi.mine(me) flatMap { made =>
|
||||
appApi.authorizedBy(me) map { used =>
|
||||
Ok(html.oAuth.app.index(made, used))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def delete(id: String) =
|
||||
Auth { _ => me =>
|
||||
appApi.deleteBy(App.Id(id), me) inject
|
||||
Redirect(s"${routes.OAuthApp.index}#made").flashSuccess
|
||||
}
|
||||
|
||||
def revoke(id: String) =
|
||||
Auth { _ => me =>
|
||||
appApi.revoke(AccessToken.Id(id), me) inject
|
||||
Redirect(routes.OAuthApp.index).flashSuccess
|
||||
}
|
||||
}
|
|
@ -59,7 +59,6 @@ object layout {
|
|||
a(activeCls("oauth.token"), href := routes.OAuthToken.index)(
|
||||
"API access tokens"
|
||||
),
|
||||
ctx.noBot option a(activeCls("oauth.app"), href := routes.OAuthApp.index)("OAuth Apps"),
|
||||
ctx.noBot option a(href := routes.DgtCtrl.index)("DGT board"),
|
||||
div(cls := "sep"),
|
||||
a(activeCls("close"), href := routes.Account.close)(
|
||||
|
|
|
@ -43,14 +43,6 @@ object security {
|
|||
)
|
||||
),
|
||||
table(sessions, curSessionId.some, clients, personalAccessTokens)
|
||||
),
|
||||
div(cls := "account security box")(
|
||||
h1("Additional third party apps"),
|
||||
p(cls := "box__pad")(
|
||||
"Revoke access of any ",
|
||||
a(href := routes.OAuthApp.index)("third party apps"),
|
||||
" that you do not trust."
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package views.html.oAuth.app
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object index {
|
||||
|
||||
def apply(made: List[lila.oauth.OAuthApp], used: List[lila.oauth.AccessToken.WithApp])(implicit
|
||||
ctx: Context
|
||||
) =
|
||||
views.html.account.layout(title = "OAuth Apps", active = "oauth.app")(
|
||||
div(cls := "account oauth")(
|
||||
used.nonEmpty option div(cls := "oauth-used box")(
|
||||
h1(id := "used")("OAuth Apps"),
|
||||
standardFlash(cls := "box__pad"),
|
||||
table(cls := "slist slist-pad")(
|
||||
used.map { t =>
|
||||
tr(
|
||||
td(
|
||||
strong(t.app.name),
|
||||
" by ",
|
||||
userIdLink(t.app.author.some),
|
||||
br,
|
||||
em(t.token.scopes.map(_.name).mkString(", "))
|
||||
),
|
||||
td(cls := "date")(
|
||||
a(href := t.app.homepageUri.toString)(t.app.homepageUri.toString),
|
||||
br,
|
||||
t.token.usedAt map { at =>
|
||||
frag("Last used ", momentFromNow(at))
|
||||
}
|
||||
),
|
||||
td(cls := "action")(
|
||||
postForm(action := routes.OAuthApp.revoke(t.token.id.value))(
|
||||
submitButton(
|
||||
cls := "button button-empty button-red confirm text",
|
||||
title := s"Revoke access from ${t.app.name}",
|
||||
dataIcon := ""
|
||||
)("Revoke")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
div(cls := "oauth-made box")(
|
||||
h1(id := "made")("My OAuth Apps"),
|
||||
p(cls := "box__pad")(
|
||||
"Want to build something that integrates with and extends Lichess? ",
|
||||
"Lichess now supports OAuth for unregistered and public clients with PKCE. ",
|
||||
"Here's a ",
|
||||
a(href := "https://github.com/lichess-org/api/tree/master/example/oauth-app")(
|
||||
"Lichess OAuth app example"
|
||||
),
|
||||
", and the ",
|
||||
a(href := routes.Api.index)("API documentation"),
|
||||
".",
|
||||
br,
|
||||
br,
|
||||
made.nonEmpty option {
|
||||
frag(
|
||||
flashMessage(cls := "flash-warning box__pad")(
|
||||
"The following apps have been created while registration was still required. ",
|
||||
"Please update them to use PKCE. ",
|
||||
"Lichess will soon drop support for the authorization code flow without PKCE. ",
|
||||
strong(a(href := "https://github.com/ornicar/lila/issues/9214")("More information")),
|
||||
"."
|
||||
),
|
||||
br,
|
||||
br
|
||||
)
|
||||
}
|
||||
),
|
||||
table(cls := "slist slist-pad")(
|
||||
made.map { t =>
|
||||
tr(
|
||||
td(
|
||||
strong(t.name),
|
||||
br,
|
||||
t.description.map { em(_) }
|
||||
),
|
||||
td(cls := "date")(
|
||||
a(href := t.homepageUri.toString)(t.homepageUri.toString),
|
||||
br,
|
||||
"Created ",
|
||||
momentFromNow(t.createdAt)
|
||||
),
|
||||
td(cls := "action")(
|
||||
postForm(action := routes.OAuthApp.delete(t.clientId.value))(
|
||||
submitButton(
|
||||
cls := "button button-empty button-red confirm",
|
||||
title := "Delete this app",
|
||||
dataIcon := ""
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package views.html
|
||||
package oAuth
|
||||
package app
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
|
@ -729,9 +729,6 @@ GET /account/oauth/token controllers.OAuthToken.index
|
|||
GET /account/oauth/token/create controllers.OAuthToken.create
|
||||
POST /account/oauth/token/create controllers.OAuthToken.createApply
|
||||
POST /account/oauth/token/:publicId/delete controllers.OAuthToken.delete(publicId: String)
|
||||
GET /account/oauth/app controllers.OAuthApp.index
|
||||
POST /account/oauth/app/:id/delete controllers.OAuthApp.delete(id: String)
|
||||
POST /account/oauth/app/:id/revoke controllers.OAuthApp.revoke(id: String)
|
||||
|
||||
# Events
|
||||
GET /event/$id<\w{8}> controllers.Event.show(id: String)
|
||||
|
|
|
@ -9,7 +9,6 @@ import lila.user.User
|
|||
case class AccessToken(
|
||||
id: AccessToken.Id,
|
||||
publicId: BSONObjectID,
|
||||
clientId: String,
|
||||
userId: User.ID,
|
||||
createdAt: Option[DateTime] = None, // for personal access tokens
|
||||
description: Option[String] = None, // for personal access tokens
|
||||
|
@ -26,17 +25,14 @@ 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: remove
|
||||
def randomPersonal() = Id(SecureRandom.nextString(16)) // TODO: prefix lip_, more entropy
|
||||
}
|
||||
|
||||
case class ForAuth(userId: User.ID, scopes: List[OAuthScope])
|
||||
|
||||
case class WithApp(token: AccessToken, app: OAuthApp)
|
||||
|
||||
object BSONFields {
|
||||
val id = "access_token_id"
|
||||
val publicId = "_id"
|
||||
val clientId = "client_id"
|
||||
val userId = "user_id"
|
||||
val createdAt = "create_date"
|
||||
val description = "description"
|
||||
|
@ -74,7 +70,6 @@ object AccessToken {
|
|||
AccessToken(
|
||||
id = r.get[Id](id),
|
||||
publicId = r.get[BSONObjectID](publicId),
|
||||
clientId = r str clientId,
|
||||
userId = r str userId,
|
||||
createdAt = r.getO[DateTime](createdAt),
|
||||
description = r strO description,
|
||||
|
@ -88,7 +83,6 @@ object AccessToken {
|
|||
$doc(
|
||||
id -> o.id,
|
||||
publicId -> o.publicId,
|
||||
clientId -> o.clientId,
|
||||
userId -> o.userId,
|
||||
createdAt -> o.createdAt,
|
||||
description -> o.description,
|
||||
|
|
|
@ -17,7 +17,6 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
|
|||
val token = AccessToken(
|
||||
id = AccessToken.Id.random(),
|
||||
publicId = BSONObjectID.generate(),
|
||||
clientId = PersonalToken.clientId, // TODO
|
||||
userId = granted.userId,
|
||||
createdAt = DateTime.now().some,
|
||||
description = granted.redirectUri.clientOrigin.some,
|
||||
|
@ -33,7 +32,6 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
|
|||
_.find(
|
||||
$doc(
|
||||
F.userId -> user.id,
|
||||
F.clientId -> PersonalToken.clientId,
|
||||
F.clientOrigin -> $exists(false)
|
||||
)
|
||||
)
|
||||
|
@ -47,7 +45,6 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
|
|||
_.countSel(
|
||||
$doc(
|
||||
F.userId -> user.id,
|
||||
F.clientId -> PersonalToken.clientId,
|
||||
F.clientOrigin -> $exists(false)
|
||||
)
|
||||
)
|
||||
|
@ -58,7 +55,6 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
|
|||
_.one[AccessToken](
|
||||
$doc(
|
||||
F.userId -> user.id,
|
||||
F.clientId -> PersonalToken.clientId,
|
||||
F.clientOrigin -> $exists(false),
|
||||
F.scopes $all scopes.toSeq
|
||||
)
|
||||
|
@ -141,8 +137,3 @@ object AccessTokenApi {
|
|||
scopes: List[OAuthScope]
|
||||
)
|
||||
}
|
||||
|
||||
object PersonalToken {
|
||||
|
||||
val clientId = "lichess_personal_token"
|
||||
}
|
||||
|
|
|
@ -11,8 +11,7 @@ import lila.db.AsyncColl
|
|||
|
||||
private case class OauthConfig(
|
||||
@ConfigName("mongodb.uri") mongoUri: String,
|
||||
@ConfigName("collection.access_token") tokenColl: CollName,
|
||||
@ConfigName("collection.app") appColl: CollName
|
||||
@ConfigName("collection.access_token") tokenColl: CollName
|
||||
)
|
||||
|
||||
@Module
|
||||
|
@ -31,9 +30,7 @@ final class Env(
|
|||
|
||||
private lazy val db = mongo.asyncDb("oauth", config.mongoUri)
|
||||
|
||||
private lazy val colls = new OauthColls(db(config.tokenColl), db(config.appColl))
|
||||
|
||||
lazy val appApi = wire[OAuthAppApi]
|
||||
private lazy val colls = new OauthColls(db(config.tokenColl))
|
||||
|
||||
lazy val server = wire[OAuthServer]
|
||||
|
||||
|
@ -54,4 +51,4 @@ final class Env(
|
|||
def forms = OAuthForm
|
||||
}
|
||||
|
||||
private class OauthColls(val token: AsyncColl, val app: AsyncColl)
|
||||
private class OauthColls(val token: AsyncColl)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
package lila.oauth
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.common.SecureRandom
|
||||
import lila.user.User
|
||||
import io.lemonlabs.uri.AbsoluteUrl
|
||||
|
||||
case class OAuthApp(
|
||||
name: String,
|
||||
clientId: OAuthApp.Id,
|
||||
clientSecret: OAuthApp.Secret,
|
||||
homepageUri: AbsoluteUrl,
|
||||
redirectUri: AbsoluteUrl,
|
||||
author: User.ID,
|
||||
createdAt: DateTime,
|
||||
description: Option[String] = None
|
||||
)
|
||||
|
||||
object OAuthApp {
|
||||
|
||||
case class Id(value: String) extends AnyVal
|
||||
case class Secret(value: String) extends AnyVal
|
||||
|
||||
def makeId = Id(SecureRandom nextString 16)
|
||||
def makeSecret = Secret(SecureRandom nextString 32)
|
||||
|
||||
object BSONFields {
|
||||
val clientId = "client_id"
|
||||
val clientSecret = "client_secret"
|
||||
val name = "name"
|
||||
val homepageUri = "homepage_uri"
|
||||
val redirectUri = "redirect_uri"
|
||||
val author = "author"
|
||||
val createdAt = "create_date"
|
||||
val description = "description"
|
||||
}
|
||||
|
||||
import reactivemongo.api.bson._
|
||||
import lila.db.BSON
|
||||
import lila.db.dsl._
|
||||
import BSON.BSONJodaDateTimeHandler
|
||||
|
||||
implicit private[oauth] val AppIdHandler = stringAnyValHandler[Id](_.value, Id.apply)
|
||||
implicit private[oauth] val AppSecretHandler = stringAnyValHandler[Secret](_.value, Secret.apply)
|
||||
|
||||
implicit val AppBSONHandler = new BSON[OAuthApp] {
|
||||
|
||||
import BSONFields._
|
||||
|
||||
def reads(r: BSON.Reader): OAuthApp =
|
||||
OAuthApp(
|
||||
clientId = r.get[Id](clientId),
|
||||
clientSecret = r.get[Secret](clientSecret),
|
||||
name = r str name,
|
||||
homepageUri = r.get[AbsoluteUrl](homepageUri),
|
||||
redirectUri =
|
||||
r.get[List[AbsoluteUrl]](redirectUri).headOption err "Missing OAuthApp.redirectUri array",
|
||||
author = r str author,
|
||||
createdAt = r.get[DateTime](createdAt),
|
||||
description = r strO description
|
||||
)
|
||||
|
||||
def writes(w: BSON.Writer, o: OAuthApp) =
|
||||
$doc(
|
||||
clientId -> o.clientId,
|
||||
clientSecret -> o.clientSecret,
|
||||
name -> o.name,
|
||||
homepageUri -> o.homepageUri,
|
||||
redirectUri -> $arr(o.redirectUri),
|
||||
author -> o.author,
|
||||
createdAt -> o.createdAt,
|
||||
description -> o.description
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package lila.oauth
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
|
||||
final class OAuthAppApi(colls: OauthColls)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import OAuthApp.{ AppBSONHandler, AppIdHandler }
|
||||
import OAuthApp.{ BSONFields => F }
|
||||
|
||||
def mine(u: User): Fu[List[OAuthApp]] =
|
||||
colls.app {
|
||||
_.find($doc(F.author -> u.id)).sort($sort desc F.createdAt).cursor[OAuthApp]().list(30)
|
||||
}
|
||||
|
||||
def create(app: OAuthApp) = colls.app(_.insert.one(app).void)
|
||||
|
||||
def findBy(clientId: OAuthApp.Id, user: User): Fu[Option[OAuthApp]] =
|
||||
colls.app {
|
||||
_.one[OAuthApp](
|
||||
$doc(
|
||||
F.clientId -> clientId,
|
||||
F.author -> user.id
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def authorizedBy(user: User): Fu[List[AccessToken.WithApp]] =
|
||||
colls.app { appColl =>
|
||||
import OAuthApp.AppBSONHandler
|
||||
colls.token {
|
||||
_.aggregateList(maxDocs = 100) { implicit framework =>
|
||||
import framework._
|
||||
Match($doc("user_id" -> user.id)) -> List(
|
||||
Sort(Descending("used_at")),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> appColl.name,
|
||||
"localField" -> "client_id",
|
||||
"foreignField" -> "client_id",
|
||||
"as" -> "app"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}.map { docs =>
|
||||
for {
|
||||
doc <- docs
|
||||
token <- AccessToken.AccessTokenBSONHandler.readOpt(doc)
|
||||
app <- doc.getAsOpt[List[OAuthApp]]("app").??(_.headOption)
|
||||
} yield AccessToken.WithApp(token, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def revoke(id: AccessToken.Id, user: User): Funit =
|
||||
colls.token {
|
||||
_.delete.one($doc("access_token_id" -> id, "user_id" -> user.id)).void
|
||||
}
|
||||
|
||||
def authorOf(clientId: OAuthApp.Id): Fu[Option[User.ID]] =
|
||||
colls.app(_.primitiveOne[User.ID]($doc(F.clientId -> clientId), F.author))
|
||||
|
||||
def update(from: OAuthApp)(f: OAuthApp => OAuthApp): Fu[OAuthApp] = {
|
||||
val app = f(from)
|
||||
if (app == from) fuccess(app)
|
||||
else colls.app(_.update.one($doc(F.clientId -> app.clientId), app)) inject app
|
||||
}
|
||||
|
||||
def deleteBy(clientId: OAuthApp.Id, user: User) =
|
||||
colls.app {
|
||||
_.delete
|
||||
.one(
|
||||
$doc(
|
||||
F.clientId -> clientId,
|
||||
F.author -> user.id
|
||||
)
|
||||
)
|
||||
.void
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ object OAuthForm {
|
|||
AccessToken(
|
||||
id = AccessToken.Id.randomPersonal(),
|
||||
publicId = BSONObjectID.generate(),
|
||||
clientId = PersonalToken.clientId,
|
||||
userId = user.id,
|
||||
createdAt = DateTime.now.some,
|
||||
description = description.some,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package lila.oauth
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import play.api.http.HeaderNames.AUTHORIZATION
|
||||
import play.api.mvc.{ RequestHeader, Result }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
@ -12,7 +11,6 @@ import lila.user.{ User, UserRepo }
|
|||
final class OAuthServer(
|
||||
colls: OauthColls,
|
||||
userRepo: UserRepo,
|
||||
appApi: OAuthAppApi,
|
||||
cacheApi: lila.memo.CacheApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
|
|
Loading…
Reference in New Issue