fix(official-bots): register with tournament server directly to get correct token
Build & Test (NowChessSystems) TeamCity build finished

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 <noreply@anthropic.com>
This commit is contained in:
Janis Eccarius
2026-06-22 21:02:13 +02:00
parent 65c3fabd91
commit 64b5d5567f
@@ -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 {