fix: verify dashboard sessions via config API

This commit is contained in:
Hermes Agent
2026-07-25 01:12:10 +00:00
parent 511baa60f3
commit a0afd8d076
2 changed files with 17 additions and 19 deletions
@@ -29,24 +29,25 @@ class GatewayHttpProbe(
bearerToken: String,
sessionCookie: String
): GatewayProbeResult {
// Hermes dashboard auth gates every protected route, including /health.
// A successful password login therefore must send its session cookie to
// the health probe as well as /v1/models; otherwise the 302-to-login
// response is incorrectly presented as a generic offline failure.
val healthCookie = if (authMode == ConnectionAuthMode.PasswordLogin) sessionCookie else ""
val health = getJson("$baseUrl/health", sessionCookie = healthCookie)
// Hermes Dashboard and Hermes API Server are distinct upstream surfaces.
// Dashboard /health and /v1/models can legitimately serve the SPA shell,
// so they are not a valid dashboard authentication probe. A password
// session is verified against the documented JSON dashboard config route.
if (authMode == ConnectionAuthMode.PasswordLogin) {
return when (val dashboard = getJson("$baseUrl/api/config", sessionCookie = sessionCookie)) {
HttpProbeResult.Success -> GatewayProbeResult.Healthy("Dashboard session authentication passed.")
else -> dashboard.toGatewayResult("Dashboard session check")
}
}
val health = getJson("$baseUrl/health")
if (health != HttpProbeResult.Success) return health.toGatewayResult("Health probe")
if (authMode == ConnectionAuthMode.None) {
return GatewayProbeResult.Healthy("Gateway health check passed.")
}
val compatibility = when (authMode) {
ConnectionAuthMode.None -> error("Anonymous probes return after the health check.")
ConnectionAuthMode.BearerToken -> getJson("$baseUrl/v1/models", bearerToken = bearerToken)
ConnectionAuthMode.PasswordLogin -> getJson("$baseUrl/v1/models", sessionCookie = sessionCookie)
}
return when (compatibility) {
HttpProbeResult.Success -> GatewayProbeResult.Healthy("Gateway health and authentication checks passed.")
else -> compatibility.toGatewayResult("Authenticated compatibility check")
return when (val models = getJson("$baseUrl/v1/models", bearerToken = bearerToken)) {
HttpProbeResult.Success -> GatewayProbeResult.Healthy("Gateway health and bearer authentication checks passed.")
else -> models.toGatewayResult("Authenticated API check")
}
}
@@ -56,7 +56,7 @@ class GatewayHttpProbeTest {
}
@Test
fun passwordSessionProbeUsesCookieOnlyForAuthenticatedCompatibilityCheck() = runBlocking {
fun passwordSessionProbeUsesCookieOnlyForDashboardConfigCheck() = runBlocking {
val requests = mutableListOf<Triple<String, String?, String?>>()
val probe = GatewayHttpProbe(client { chain ->
requests += Triple(
@@ -71,10 +71,7 @@ class GatewayHttpProbeTest {
assertTrue(result is GatewayProbeResult.Healthy)
assertEquals(
listOf(
Triple("/health", null, SESSION_COOKIE),
Triple("/v1/models", null, SESSION_COOKIE)
),
listOf(Triple("/api/config", null, SESSION_COOKIE)),
requests
)
}