diff --git a/app/controllers/Account.scala b/app/controllers/Account.scala index ff5b7b2c19..acd73df6c8 100644 --- a/app/controllers/Account.scala +++ b/app/controllers/Account.scala @@ -364,13 +364,20 @@ final class Account( def security = Auth { implicit ctx => me => - env.security.api.dedup(me.id, ctx.req) >> - env.security.api.locatedOpenSessions(me.id, 50) map { sessions => - Ok( - html.account - .security(me, sessions, currentSessionId, thirdPartyApps = true, personalAccessTokens = true) + for { + _ <- env.security.api.dedup(me.id, ctx.req) + sessions <- env.security.api.locatedOpenSessions(me.id, 50) + personalAccessTokens <- env.oAuth.tokenApi.count(me) + } yield Ok( + html.account + .security( + me, + sessions, + currentSessionId, + thirdPartyApps = true, + personalAccessTokens = personalAccessTokens ) - } + ) } def signout(sessionId: String) = diff --git a/app/controllers/OAuth.scala b/app/controllers/OAuth.scala index 74dc3e17aa..5edb0dbb98 100644 --- a/app/controllers/OAuth.scala +++ b/app/controllers/OAuth.scala @@ -12,7 +12,7 @@ import org.joda.time.DateTime import scalatags.Text.all.stringFrag import lila.app._ import lila.api.Context -import lila.oauth.{ AccessToken, AccessTokenRequest, AuthorizationRequest } +import lila.oauth.{ AccessToken, AccessTokenRequest, AuthorizationRequest, PersonalToken } final class OAuth(env: Env) extends LilaController(env) { @@ -76,7 +76,7 @@ final class OAuth(env: Env) extends LilaController(env) { val token = AccessToken( id = AccessToken.Id(lila.oauth.Protocol.Secret.random("lio_").value), publicId = BSONObjectID.generate(), - clientId = "lichess_personal_token", // TODO + clientId = PersonalToken.clientId, // TODO userId = granted.userId, createdAt = DateTime.now().some, description = granted.redirectUri.clientOrigin.some, diff --git a/app/views/account/security.scala b/app/views/account/security.scala index 1c3ded86f2..3c7cf69ef7 100644 --- a/app/views/account/security.scala +++ b/app/views/account/security.scala @@ -15,7 +15,7 @@ object security { sessions: List[lila.security.LocatedSession], curSessionId: String, thirdPartyApps: Boolean, - personalAccessTokens: Boolean + personalAccessTokens: Int )(implicit ctx: Context ) = @@ -26,23 +26,23 @@ object security { standardFlash(cls := "box__pad"), div(cls := "box__pad")( p( - "This is a list of devices that are logged into your account. If you notice any suspicious activity, make sure to ", + "This is a list of devices and applications that are logged into your account. If you notice any suspicious activity, make sure to ", a(href := routes.Account.email)("check your recovery email address"), " and ", a(href := routes.Account.passwd)("change your password"), "." ), sessions.sizeIs > 1 option div( - trans.alternativelyYouCanX { - postForm(cls := "revoke-all", action := routes.Account.signout("all"))( - submitButton(cls := "button button-empty button-red confirm")( - trans.revokeAllSessions() - ) + "You can also ", + postForm(cls := "revoke-all", action := routes.Account.signout("all"))( + submitButton(cls := "button button-empty button-red confirm")( + trans.revokeAllSessions() ) - } + ), + "." ) ), - table(sessions, curSessionId.some) + table(sessions, curSessionId.some, personalAccessTokens) ), thirdPartyApps option div(cls := "account security box")( h1("Third party apps"), @@ -51,19 +51,15 @@ object security { a(href := routes.OAuthApp.index)("third party apps"), " that you do not trust." ) - ), - personalAccessTokens option div(cls := "account security box")( - h1("Personal access tokens"), - p(cls := "box__pad")( - "Revoke any ", - a(href := routes.OAuthToken.index)("personal access tokens"), - " that you do not recognize." - ) ) ) } - def table(sessions: List[lila.security.LocatedSession], curSessionId: Option[String])(implicit lang: Lang) = + def table( + sessions: List[lila.security.LocatedSession], + curSessionId: Option[String], + personalAccessTokens: Int + )(implicit lang: Lang) = st.table(cls := "slist slist-pad")( sessions.map { s => tr( @@ -94,6 +90,16 @@ object security { ) } ) - } + }, + (personalAccessTokens > 0) option tr( + td(cls := "icon")(span(cls := "is-green", dataIcon := "")), + td(cls := "info")( + strong("Personal access tokens"), + " have been issued for your account. Revoke any that you do not recognize." + ), + td( + a(href := routes.OAuthToken.index, cls := "button", title := "API access tokens", dataIcon := "") + ) + ) ) } diff --git a/modules/i18n/src/main/I18nKeys.scala b/modules/i18n/src/main/I18nKeys.scala index 00cb1e84f4..92f7fc7022 100644 --- a/modules/i18n/src/main/I18nKeys.scala +++ b/modules/i18n/src/main/I18nKeys.scala @@ -552,7 +552,6 @@ val `inKidModeTheLichessLogoGetsIconX` = new I18nKey("inKidModeTheLichessLogoGet val `enableKidMode` = new I18nKey("enableKidMode") val `disableKidMode` = new I18nKey("disableKidMode") val `security` = new I18nKey("security") -val `alternativelyYouCanX` = new I18nKey("alternativelyYouCanX") val `sessions` = new I18nKey("sessions") val `revokeAllSessions` = new I18nKey("revokeAllSessions") val `playChessEverywhere` = new I18nKey("playChessEverywhere") diff --git a/modules/oauth/src/main/PersonalTokenApi.scala b/modules/oauth/src/main/PersonalTokenApi.scala index ce53ef6953..afc1263f40 100644 --- a/modules/oauth/src/main/PersonalTokenApi.scala +++ b/modules/oauth/src/main/PersonalTokenApi.scala @@ -49,6 +49,15 @@ final class PersonalTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Ex } } } + + def count(u: User): Fu[Int] = + colls.token { + _.countSel($doc( + F.userId -> u.id, + F.clientId -> PersonalToken.clientId, + F.clientOrigin -> $exists(false), + )) + } } object PersonalToken { diff --git a/translation/source/site.xml b/translation/source/site.xml index a9ea1a9550..f0c06d3862 100644 --- a/translation/source/site.xml +++ b/translation/source/site.xml @@ -693,7 +693,6 @@ computer analysis, game chat and shareable URL. Enable Kid mode Disable Kid mode Security - Alternatively you can %s. Sessions revoke all sessions Play chess everywhere