fix(auth): support __Host-/__Secure- prefixed session cookies and proxy-relative paths (issue #635) (#648)

- CookieSerialization: add SESSION_COOKIE_NAMES (__Host-, __Secure-, bare) and
  isSessionCookieName(); isSessionCookie now matches any variant.
- PersistentCookieJar.getSessionCookieValue(): iterate SESSION_COOKIE_NAMES,
  returning the strictest prefixed match when present.
- HermesWsClient.refreshWsTicketIfNeeded(): drop the bare-name session-cookie
  preflight in gated mode — the server may hold a valid refresh cookie under a
  prefixed name; the authenticated ticket endpoint is the authority. Ticket URL
  already derived from endpoint (issue #634).
- HermesWsClient: stop logging WS close/failure reason strings and exception
  messages (class name only) to avoid leaking server context / URLs.
- ProfileScopeInterceptor: match profile-scoped paths via endpoint-relative
  request path so reverse-proxy prefixes (e.g. /hermes/api/...) are honored;
  wrap endpoint read in runCatching.

Verified: ktlintCheck, testDebugUnitTest, lintDebug all green locally.

Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-20 20:56:39 +03:00
1 parent 86c780c10b
commit c8a5055a84
7 files changed
+225 -15

No files matched your search

@@ -253,13 +253,6 @@ object HermesWsClient {
return true
}
val sessionCookie = AuthManager.getSessionCookie()
if (sessionCookie.isNullOrBlank()) {
Log.w(TAG, "WS ticket refresh aborted: no session cookie found in gated mode")
_connectionStatus.value = ConnectionStatus.AUTH_EXPIRED
return false
}
try {
val client = OkHttpProvider.probe
val request =
@@ -294,7 +287,7 @@ object HermesWsClient {
return false
}
} catch (e: Exception) {
Log.w(TAG, "WS ticket refresh failed: ${e.message}")
Log.w(TAG, "WS ticket refresh failed: ${e.javaClass.simpleName}")
_connectionStatus.value = ConnectionStatus.AUTH_EXPIRED
return false
}
@@ -539,7 +532,8 @@ object HermesWsClient {
code: Int,
reason: String,
) {
Log.d(TAG, "WebSocket closing: $code $reason")
// Do NOT log [reason] — it may carry server-side context.
Log.d(TAG, "WebSocket closing: $code")
webSocket.close(code, reason)
}
@@ -548,7 +542,9 @@ object HermesWsClient {
code: Int,
reason: String,
) {
Log.i(TAG, "WebSocket closed: $code $reason")
// Do NOT log [reason] — it may carry server-side context. The
// reason is still inspected internally to detect auth failures.
Log.i(TAG, "WebSocket closed: $code")
connected.set(false)
stopHealthTracking()
if (code == 4001 || code == 4401 ||
@@ -567,7 +563,10 @@ object HermesWsClient {
t: Throwable,
response: Response?,
) {
Log.e(TAG, "WebSocket failure: ${t.message}", t)
// Log the exception class only — [Throwable.message] can leak URLs
// or headers. The message is still inspected internally for auth
// detection.
Log.e(TAG, "WebSocket failure: ${t.javaClass.simpleName}", t)
connected.set(false)
stopHealthTracking()
val code = response?.code ?: 0