From b98bdd2a64eb6c8279bd3cfe15d70628025ef0e5 Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 23 Jun 2026 13:47:13 +0200 Subject: [PATCH] fix(tournament): use HS256 director token for native tournament-server calls The tournament-server only accepts HS256 tokens it issued; forwarding a NowChessSystems RS256 user token caused "unsupported algorithm". Proxied calls (start/join/withdraw/stream) targeting the native server now swap in the director token registered on that server. Co-Authored-By: Claude Opus 4.8 --- .../service/ExternalTournamentClient.scala | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/tournament/src/main/scala/de/nowchess/tournament/service/ExternalTournamentClient.scala b/modules/tournament/src/main/scala/de/nowchess/tournament/service/ExternalTournamentClient.scala index bbecd80..431cce2 100644 --- a/modules/tournament/src/main/scala/de/nowchess/tournament/service/ExternalTournamentClient.scala +++ b/modules/tournament/src/main/scala/de/nowchess/tournament/service/ExternalTournamentClient.scala @@ -6,6 +6,7 @@ import jakarta.enterprise.context.ApplicationScoped import jakarta.inject.Inject import jakarta.ws.rs.client.{Client, ClientBuilder, Entity} import jakarta.ws.rs.core.MediaType +import org.eclipse.microprofile.config.inject.ConfigProperty import scala.compiletime.uninitialized import scala.util.Try @@ -15,10 +16,35 @@ class ExternalTournamentClient: // scalafix:off DisableSyntax.var @Inject var objectMapper: ObjectMapper = uninitialized @volatile private var directorToken: Option[String] = None + + @ConfigProperty(name = "nowchess.tournament.native-server-url", defaultValue = "http://141.37.123.132:8086") + var nativeServerUrl: String = uninitialized + + @ConfigProperty(name = "nowchess.tournament.director-name", defaultValue = "NowChess System") + var directorName: String = uninitialized // scalafix:on private def buildClient(): Client = ClientBuilder.newClient() + // The tournament-server only accepts HS256 tokens it issued. Never forward a NowChessSystems + // RS256 user token to it — swap in the director token registered on that server. + private def normalize(url: String): String = url.stripSuffix("/") + + private def isNativeServer(serverUrl: String): Boolean = + nativeServerUrl.nonEmpty && normalize(serverUrl) == normalize(nativeServerUrl) + + private def directorBearer(): Option[String] = + directorToken + .orElse { + val fresh = registerDirector(nativeServerUrl, directorName) + directorToken = fresh + fresh + } + .map(t => s"Bearer $t") + + private def authFor(serverUrl: String, userAuth: Option[String]): Option[String] = + if isNativeServer(serverUrl) then directorBearer() else userAuth + def publishNative(serverUrl: String, directorName: String, form: String): Boolean = val token = directorToken.orElse { val fresh = registerDirector(serverUrl, directorName) @@ -105,7 +131,7 @@ class ExternalTournamentClient: Try { val client = buildClient() val builder = client.target(s"$serverUrl/$path").request(MediaType.APPLICATION_JSON) - val withAuth = authHeader.fold(builder)(h => builder.header("Authorization", h)) + val withAuth = authFor(serverUrl, authHeader).fold(builder)(h => builder.header("Authorization", h)) val response = withAuth.post(Entity.json("")) try (response.getStatus, response.readEntity(classOf[String])) finally @@ -132,7 +158,7 @@ class ExternalTournamentClient: Try { val client = buildClient() val builder = client.target(s"$serverUrl/$path").request("application/x-ndjson") - val withAuth = authHeader.fold(builder)(h => builder.header("Authorization", h)) + val withAuth = authFor(serverUrl, authHeader).fold(builder)(h => builder.header("Authorization", h)) val response = withAuth.get() if response.getStatus == 200 then Some(response.readEntity(classOf[java.io.InputStream])) else