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