fix: refresh loopback dashboard token after restart (#568)
* fix: refresh loopback dashboard token after restart The gateway regenerates its ephemeral session token on every restart, so the token the app persisted becomes stale. After a dashboard restart REST calls 401 and the WebSocket auto-reconnect loop hangs in AUTH_EXPIRED until the user manually re-authenticates. - Add DashboardSessionTokenRefresher: re-reads the injected window.__HERMES_SESSION_TOKEN__ from the dashboard SPA and writes it back to AuthManager. - TokenRefreshAuthenticator: on a 401 retry, scrape a fresh token from the dashboard and retry instead of giving up. - HermesWsClient: refresh the token before each non-gated WebSocket handshake so reconnects don't get stuck on a stale token; guard serverStore read against IllegalStateException. Covers the loopback (--insecure) deployment path; gated/ticket mode is untouched. * fix: harden dashboard token refresher (sync, body cap, blank-token test) Second-opinion review flagged robustness gaps (no correctness/security regressions in the happy path): - DashboardSessionTokenRefresher.refresh(): @Synchronized to dedupe concurrent 401 / reconnect storms instead of each firing its own GET. - Cap SPA HTML read at 64 KiB before regex scan (defensive against OOM on low-end devices); we only need the injected token near the document top. - HermesWsClient: log (not silently swallow) the transient uninitialized serverStore case so a real misconfig is still visible. - Add test: blank-injected token returns null. --------- Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
1 parent
ee28ee5ce2
commit
907d247312
5 files changed
+174
-4
No files matched your search
@@ -4,6 +4,7 @@ import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.m57.hermescontrol.BuildConfig
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.remote.DashboardSessionTokenRefresher
|
||||
import com.m57.hermescontrol.data.remote.NetworkMonitor
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
@@ -234,8 +235,21 @@ object HermesWsClient {
|
||||
* and ticket refresh failed or cannot be performed.
|
||||
*/
|
||||
private fun refreshWsTicketIfNeeded(): Boolean {
|
||||
val isGated = AuthManager.serverStore.getLatestState().wsAuthParam == "ticket"
|
||||
val isGated =
|
||||
try {
|
||||
AuthManager.serverStore.getLatestState().wsAuthParam == "ticket"
|
||||
} catch (_: IllegalStateException) {
|
||||
// serverStore not initialized yet (transient early call); treat
|
||||
// as loopback so a stale-token refresh still runs. Log so a real
|
||||
// misconfiguration isn't silently swallowed.
|
||||
Log.w(TAG, "serverStore uninitialized during WS handshake; assuming non-gated")
|
||||
false
|
||||
}
|
||||
if (!isGated) {
|
||||
// The loopback dashboard token is regenerated on every server
|
||||
// restart. Refresh it before each WebSocket handshake so automatic
|
||||
// reconnect does not get stuck in AUTH_EXPIRED with a stale token.
|
||||
DashboardSessionTokenRefresher.refresh()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user