From 64b5d5567f110c2fe152558c7de275a1e0b30e21 Mon Sep 17 00:00:00 2001 From: Janis Eccarius Date: Mon, 22 Jun 2026 21:02:13 +0200 Subject: [PATCH] fix(official-bots): register with tournament server directly to get correct token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TOURNAMENT_SERVICE_URL points to the NowChessTools tournament server which uses its own HMAC-HS256 JWTs issued by POST /api/auth/register. Tokens from the NowChessSystems account service (RS256) are rejected with 401 by that server. resolveToken now first calls POST {tournamentServiceUrl}/api/auth/register (public endpoint, idempotent — finds existing identity by name or creates). This returns the correct HMAC-HS256 token for the target server and is stored in Redis. Falls back to the account service path for deployments where TOURNAMENT_SERVICE_URL points to the NowChessSystems tournament module. Co-Authored-By: Claude Sonnet 4.6 --- .../bot/service/TournamentBotGamePlayer.scala | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/modules/official-bots/src/main/scala/de/nowchess/bot/service/TournamentBotGamePlayer.scala b/modules/official-bots/src/main/scala/de/nowchess/bot/service/TournamentBotGamePlayer.scala index 9f94733..8eaea1b 100644 --- a/modules/official-bots/src/main/scala/de/nowchess/bot/service/TournamentBotGamePlayer.scala +++ b/modules/official-bots/src/main/scala/de/nowchess/bot/service/TournamentBotGamePlayer.scala @@ -62,10 +62,11 @@ class TournamentBotGamePlayer: private def resolveToken(difficulty: String): Option[String] = val name = botName(difficulty) val redisKey = s"${redisConfig.prefix}:tournament-bot:token:$name" - fetchTokenFromAccountService(name) + registerWithTournamentServer(name) + .orElse(fetchTokenFromAccountService(name)) .map { token => redis.value(classOf[String]).set(redisKey, token) - log.infof("Fetched fresh bot token for %s from account service", name) + log.infof("Refreshed bot token for %s — stored in Redis", name) token } .orElse { @@ -81,6 +82,21 @@ class TournamentBotGamePlayer: } } + private def registerWithTournamentServer(name: String): Option[String] = + Try { + val body = s"""{"name":"${name.replace("\"", "\\\"")}","isBot":true}""" + val response = client.target(tournamentServiceUrl) + .path("api").path("auth").path("register") + .request(MediaType.APPLICATION_JSON) + .post(Entity.entity(body, MediaType.APPLICATION_JSON)) + val status = response.getStatus + if status == 200 || status == 201 then + val token = objectMapper.readTree(response.readEntity(classOf[String])).path("token").asText() + response.close() + Option(token).filter(_.nonEmpty) + else { response.close(); None } + }.toOption.flatten + private def fetchTokenFromAccountService(name: String): Option[String] = Try(accountServiceClient.getBotToken(name).token).toOption.filter(_.nonEmpty) .orElse {