diff --git a/modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala b/modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala index 03ab587..3ed5a04 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala @@ -186,8 +186,11 @@ class InstanceHeartbeatService: ) val json = mapper.writeValueAsString(metadata) - reactiveRedis.value(classOf[String]).setex(key, 5L, json) - .subscribe().`with`( + reactiveRedis + .value(classOf[String]) + .setex(key, 5L, json) + .subscribe() + .`with`( _ => (), (ex: Throwable) => log.warnf(ex, "Failed to refresh Redis heartbeat"), ) diff --git a/modules/core/src/test/scala/de/nowchess/chess/resource/InternalAuthFilterHttpTest.scala b/modules/core/src/test/scala/de/nowchess/chess/resource/InternalAuthFilterHttpTest.scala new file mode 100644 index 0000000..f21daf7 --- /dev/null +++ b/modules/core/src/test/scala/de/nowchess/chess/resource/InternalAuthFilterHttpTest.scala @@ -0,0 +1,67 @@ +package de.nowchess.chess.resource + +import de.nowchess.chess.grpc.{IoGrpcClientWrapper, RuleSetGrpcAdapter} +import de.nowchess.chess.redis.GameRedisSubscriberManager +import io.quarkus.test.InjectMock +import io.quarkus.test.junit.{QuarkusTest, TestProfile} +import io.quarkus.test.junit.QuarkusTestProfile +import io.restassured.RestAssured +import jakarta.ws.rs.core.MediaType +import org.junit.jupiter.api.{DisplayName, Test} + +import java.util.Map as JMap + +// scalafix:off +class InternalAuthEnabledProfile extends QuarkusTestProfile: + override def getConfigOverrides(): JMap[String, String] = + JMap.of( + "nowchess.internal.auth.enabled", "true", + "nowchess.internal.secret", "test-secret-123", + ) + +@QuarkusTest +@TestProfile(classOf[InternalAuthEnabledProfile]) +@DisplayName("InternalAuthFilter HTTP") +class InternalAuthFilterHttpTest: + + @InjectMock + var ioWrapper: IoGrpcClientWrapper = scala.compiletime.uninitialized + + @InjectMock + var ruleAdapter: RuleSetGrpcAdapter = scala.compiletime.uninitialized + + @InjectMock + var subscriberManager: GameRedisSubscriberManager = scala.compiletime.uninitialized + + @Test + @DisplayName("POST /api/board/game without secret returns 401") + def rejectNoSecret(): Unit = + RestAssured.`given`() + .contentType(MediaType.APPLICATION_JSON) + .body("{}") + .when() + .post("/api/board/game") + .then() + .statusCode(401) + + @Test + @DisplayName("POST /api/board/game with wrong secret returns 401") + def rejectWrongSecret(): Unit = + RestAssured.`given`() + .contentType(MediaType.APPLICATION_JSON) + .header("X-Internal-Secret", "wrong-secret") + .body("{}") + .when() + .post("/api/board/game") + .then() + .statusCode(401) + + @Test + @DisplayName("GET /api/board/game/{id} without secret returns 404 not 401") + def nonInternalEndpointNotBlocked(): Unit = + RestAssured.`given`() + .when() + .get("/api/board/game/nonexistent") + .then() + .statusCode(404) +// scalafix:on diff --git a/modules/security/src/main/scala/de/nowchess/security/InternalAuthFilter.scala b/modules/security/src/main/scala/de/nowchess/security/InternalAuthFilter.scala index aebe02b..e745200 100644 --- a/modules/security/src/main/scala/de/nowchess/security/InternalAuthFilter.scala +++ b/modules/security/src/main/scala/de/nowchess/security/InternalAuthFilter.scala @@ -1,6 +1,6 @@ package de.nowchess.security -import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Singleton import jakarta.ws.rs.container.{ContainerRequestContext, ContainerRequestFilter} import jakarta.ws.rs.core.Response import jakarta.ws.rs.ext.Provider @@ -9,7 +9,7 @@ import scala.compiletime.uninitialized @Provider @InternalOnly -@ApplicationScoped +@Singleton class InternalAuthFilter extends ContainerRequestFilter: @ConfigProperty(name = "nowchess.internal.secret", defaultValue = "")