Compare commits

...

1 Commits

Author SHA1 Message Date
LQ63 a80b235bc3 fix: enable official bots to connect to external tournament server
Build & Test (NowChessSystems) TeamCity build failed
Two bugs prevented official bots from joining the external tournament-server:

1. JWT claim mismatch — bot tokens lacked the `isBot: true` claim the
   tournament server requires. Added the claim to generateBotToken() in
   AccountService, which covers both user-owned bots and official bots.

2. Broken join flow — TournamentBotGamePlayer.joinTournament() called
   registerBot() which hit POST /api/auth/register on the tournament server,
   an endpoint that does not exist. Removed registerBot() and updated
   JoinTournamentRequest to accept a botToken field so the caller supplies
   the pre-existing NowChessSystems token directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 02:04:07 +02:00
4 changed files with 8 additions and 26 deletions
@@ -239,6 +239,7 @@ class AccountService:
.subject(botId.toString)
.expiresAt(Long.MaxValue)
.claim("type", "bot")
.claim("isBot", true)
.claim("name", botName)
.sign()
@@ -2,6 +2,7 @@ package de.nowchess.bot.resource
case class JoinTournamentRequest(
tournamentId: String,
botToken: String,
difficulty: String,
serverUrl: Option[String],
)
@@ -33,7 +33,7 @@ class TournamentJoinResource:
difficulty,
serverUrl,
)
player.joinTournament(req.tournamentId, difficulty, serverUrl) match
player.joinTournament(req.tournamentId, req.botToken, difficulty, serverUrl) match
case Right(botId) =>
val resp = JoinTournamentResponse(botId, difficulty, "joining")
Response.ok(resp).build()
@@ -50,11 +50,11 @@ class TournamentBotGamePlayer:
log.infof("Tournament bot enabled — server=%s tournament=%s bot=%s", cfg.serverUrl, cfg.tournamentId, cfg.botId)
startAsync(cfg)
def joinTournament(tournamentId: String, difficulty: String, serverUrl: String): Either[String, String] =
registerBot(serverUrl, difficulty) match
case None => Left("Failed to register bot with tournament server")
case Some((botId, token)) =>
val cfg = TournamentBotConfig(serverUrl, tournamentId, token, botId, difficulty)
def joinTournament(tournamentId: String, botToken: String, difficulty: String, serverUrl: String): Either[String, String] =
TournamentBotConfig.jwtSubject(botToken) match
case None => Left("Invalid bot token — could not extract subject")
case Some(botId) =>
val cfg = TournamentBotConfig(serverUrl, tournamentId, botToken, botId, difficulty)
if join(cfg) then
startAsync(cfg)
Right(botId)
@@ -65,26 +65,6 @@ class TournamentBotGamePlayer:
thread.setDaemon(true)
thread.start()
private def registerBot(serverUrl: String, difficulty: String): Option[(String, String)] =
Try {
val name = s"NowChess ${difficulty.capitalize}"
val body = s"""{"name":"$name","isBot":true}"""
val response = client
.target(serverUrl)
.path("api")
.path("auth")
.path("register")
.request(MediaType.APPLICATION_JSON)
.post(Entity.entity(body, MediaType.APPLICATION_JSON))
if response.getStatus == 201 then
val node = objectMapper.readTree(response.readEntity(classOf[String]))
val id = node.path("id").asText()
val token = node.path("token").asText()
response.close()
if id.nonEmpty && token.nonEmpty then Some((id, token)) else None
else { log.warnf("Bot registration returned status %d", response.getStatus); response.close(); None }
}.getOrElse(None)
@PreDestroy
def cleanup(): Unit =
running = false