Skip to content

Commit

Permalink
rotate oauth token on creation - closes #14946
Browse files Browse the repository at this point in the history
up to 30 tokens per user/clientOrigin pair
keeps the most recently used or created tokens
  • Loading branch information
ornicar committed Mar 24, 2024
1 parent f660339 commit 038ad62
Showing 1 changed file with 21 additions and 8 deletions.
29 changes: 21 additions & 8 deletions modules/oauth/src/main/AccessTokenApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,24 @@ final class AccessTokenApi(
import OAuthScope.given
import AccessToken.{ BSONFields as F, given }

private def create(token: AccessToken): Fu[AccessToken] = coll.insert.one(token).inject(token)
private def createAndRotate(token: AccessToken): Fu[AccessToken] = for
oldIds <- coll
.find($doc(F.userId -> token.userId, F.clientOrigin -> token.clientOrigin), $doc(F.id -> true).some)
.sort($doc(F.usedAt -> -1, F.createdAt -> -1))
.skip(30)
.cursor[Bdoc]()
.listAll()
.dmap:
_.flatMap { _.getAsOpt[AccessToken.Id](F.id) }
_ <- oldIds.nonEmpty.so:
coll.delete.one($doc(F.id.$in(oldIds))).void
_ <- coll.insert.one(token)
yield token

def create(setup: OAuthTokenForm.Data, me: User, isStudent: Boolean): Fu[AccessToken] =
(fuccess(isStudent) >>| userRepo.isManaged(me.id)) flatMap { noBot =>
(fuccess(isStudent) >>| userRepo.isManaged(me.id)).flatMap { noBot =>
val plain = Bearer.randomPersonal()
create:
createAndRotate:
AccessToken(
id = AccessToken.Id.from(plain),
plain = plain,
Expand All @@ -42,7 +54,7 @@ final class AccessTokenApi(

def create(granted: AccessTokenRequest.Granted): Fu[AccessToken] =
val plain = Bearer.random()
create:
createAndRotate:
AccessToken(
id = AccessToken.Id.from(plain),
plain = plain,
Expand Down Expand Up @@ -73,7 +85,7 @@ final class AccessTokenApi(
)
.getOrElse:
val plain = Bearer.randomPersonal()
create:
createAndRotate:
AccessToken(
id = AccessToken.Id.from(plain),
plain = plain,
Expand Down Expand Up @@ -158,7 +170,8 @@ final class AccessTokenApi(
F.id -> id,
F.userId -> user.id
)
.void.andDo(onRevoke(id))
.void
.andDo(onRevoke(id))

def revokeByClientOrigin(clientOrigin: String, user: User): Funit =
coll
Expand Down Expand Up @@ -190,11 +203,11 @@ final class AccessTokenApi(
def test(bearers: List[Bearer]): Fu[Map[Bearer, Option[AccessToken]]] =
coll
.optionsByOrderedIds[AccessToken, AccessToken.Id](
bearers map AccessToken.Id.from,
bearers.map(AccessToken.Id.from),
readPref = _.sec
)(_.id)
.flatMap: tokens =>
userRepo.filterDisabled(tokens.flatten.map(_.userId)) map { closedUserIds =>
userRepo.filterDisabled(tokens.flatten.map(_.userId)).map { closedUserIds =>
val openTokens = tokens.map(_.filter(token => !closedUserIds(token.userId)))
bearers.zip(openTokens).toMap
}
Expand Down

0 comments on commit 038ad62

Please sign in to comment.