From cf11bab18326bb27f8301799791011913d8654a7 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Tue, 19 May 2026 12:28:23 +0200 Subject: [PATCH] add endpoint to create managed student oauth tokens for TKS to make their students join regional teams --- app/controllers/Clas.scala | 13 +++++++++++++ conf/clas.routes | 1 + modules/oauth/src/main/AccessTokenApi.scala | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/app/controllers/Clas.scala b/app/controllers/Clas.scala index fdd1615b4fb..5463777296f 100644 --- a/app/controllers/Clas.scala +++ b/app/controllers/Clas.scala @@ -221,6 +221,19 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env): ) } + def makeStudentOauthTokens(id: ClasId) = SecuredScopedBody(_.Teacher)(_.Team.Lead) { ctx ?=> me ?=> + WithClassAndStudents(id): (clas, students) => + students + .filter(_.managed) + .sequentially: student => + for + token <- env.oAuth.tokenApi.clasStudentToken(clas.name, student.userId) + user <- env.user.lightUserApi.asyncFallback(student.userId) + yield s"${user.name}, ${student.realName}, ${token.plain}" + .map: lines => + Ok(lines.mkString("\n")).asAttachment(s"lichess-student-tokens.${clas.id}.csv") + } + def students(id: ClasId) = Secure(_.Teacher) { ctx ?=> me ?=> WithClass(id): clas => for diff --git a/conf/clas.routes b/conf/clas.routes index 33edac68149..3d143cd11f6 100644 --- a/conf/clas.routes +++ b/conf/clas.routes @@ -37,3 +37,4 @@ POST /class/$id<\w{8}>/student/:username/move/$to<\w{8}> controllers.clas.Clas. POST /class/$id<\w{8}>/login controllers.clas.Clas.loginCreate(id: ClasId) GET /class/$id<\w{8}>/bulk-actions controllers.clas.Clas.bulkActions(id: ClasId) POST /class/$id<\w{8}>/bulk-actions controllers.clas.Clas.bulkActionsPost(id: ClasId) +POST /class/$id<\w{8}>/oauth-tokens controllers.clas.Clas.makeStudentOauthTokens(id: ClasId) diff --git a/modules/oauth/src/main/AccessTokenApi.scala b/modules/oauth/src/main/AccessTokenApi.scala index b102b2ba9c3..432863f810d 100644 --- a/modules/oauth/src/main/AccessTokenApi.scala +++ b/modules/oauth/src/main/AccessTokenApi.scala @@ -105,6 +105,21 @@ final class AccessTokenApi( .map(user.id -> _) yield tokens.toMap + def clasStudentToken(clasName: String, student: UserId)(using UserAgent): Fu[AccessToken] = + given MyId = student.into(MyId) + val scopes = OAuthScopes(List(OAuthScope.Team.Read, OAuthScope.Team.Write)) + findCompatiblePersonal(scopes).flatMap: + _.filter(_.description.contains(clasName)) match + case Some(token) => fuccess(token) + case None => + create( + OAuthTokenForm.Data( + description = clasName, + scopes = scopes.value.map(_.key) + ), + isStudent = true + ) + def listPersonal(using me: MyId): Fu[List[AccessToken]] = coll .find: