From a0afd8d0760810c2d19462e88a2b04430b8afb2b Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 25 Jul 2026 01:12:10 +0000 Subject: [PATCH] fix: verify dashboard sessions via config API --- .../connection/GatewayHttpProbe.kt | 29 ++++++++++--------- .../connection/GatewayHttpProbeTest.kt | 7 ++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbe.kt b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbe.kt index 1ccee30..edb7ecb 100644 --- a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbe.kt +++ b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbe.kt @@ -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") } } diff --git a/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbeTest.kt b/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbeTest.kt index 89393dc..79decc8 100644 --- a/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbeTest.kt +++ b/apps/mobile/android/app/src/test/java/cloud/molberg/hermesmobile/connection/GatewayHttpProbeTest.kt @@ -56,7 +56,7 @@ class GatewayHttpProbeTest { } @Test - fun passwordSessionProbeUsesCookieOnlyForAuthenticatedCompatibilityCheck() = runBlocking { + fun passwordSessionProbeUsesCookieOnlyForDashboardConfigCheck() = runBlocking { val requests = mutableListOf>() 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 ) }