From f4fe3974445deacc923e418ba4e679bdcbcb1453 Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Tue, 7 Jul 2026 07:34:54 +0200 Subject: [PATCH] fix(onboarding): honor the insecure-HTTP opt-in during discovery, not just after commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emulator smoke test caught a real regression from the cleartext-gate hardening: the CleartextGuardInterceptor reads SessionStore.allowInsecureHttp, which is only PERSISTED when an instance is committed. During onboarding discovery the instance isn't committed yet, so the flag was still false and the guard blocked the /health probe to a plain-HTTP host even with the "Allow insecure HTTP" switch ON — the toggle affected the derived URL and isTransportAllowed() but not the actual request gate, so discovery could never succeed. Add a transient, in-memory SessionStore.pendingAllowInsecureHttp set by AuthRepository.discover() from the onboarding toggle; the NetworkModule guard now allows cleartext when EITHER the persisted flag or the pending flag is set. Reset on clearAll() (forget instance). Verified on the emulator: with the toggle OFF a plain-HTTP host to a non-loopback address issues no request (guard blocks → "Couldn't reach"); with it ON the GET is actually attempted. Loopback HTTP (10.0.2.2) discovery returns 200 as before; HTTPS is unaffected. --- .../com/pinakes/app/data/network/NetworkModule.kt | 5 ++++- .../com/pinakes/app/data/repository/AuthRepository.kt | 3 +++ .../java/com/pinakes/app/data/store/SessionStore.kt | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt b/app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt index d765f96..fe31609 100644 --- a/app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt +++ b/app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt @@ -28,7 +28,10 @@ class NetworkModule(private val session: SessionStore) { private val okHttpClient: OkHttpClient by lazy { val builder = OkHttpClient.Builder() - .addInterceptor(CleartextGuardInterceptor { session.allowInsecureHttp }) + // Also honour the transient onboarding opt-in: during discovery the instance isn't + // committed yet, so session.allowInsecureHttp is still false — pendingAllowInsecureHttp + // carries the toggle so the discovery probe to a plain-HTTP host isn't blocked. + .addInterceptor(CleartextGuardInterceptor { session.allowInsecureHttp || session.pendingAllowInsecureHttp }) .addInterceptor(AuthInterceptor(session)) .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) diff --git a/app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt b/app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt index e8c850b..c2f0aaa 100644 --- a/app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt +++ b/app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt @@ -30,6 +30,9 @@ class AuthRepository( * to continue based on [HealthPayload.appAccessEnabled] and the transport warning. */ suspend fun discover(rawInstanceUrl: String, allowInsecure: Boolean = false): ApiResult { + // Carry the onboarding toggle into the cleartext gate: the instance isn't committed + // yet, so the persisted flag is still false and would block a plain-HTTP probe. + session.pendingAllowInsecureHttp = allowInsecure val apiBaseUrl = NetworkModule.deriveApiBaseUrl(rawInstanceUrl, allowInsecure) val origin = NetworkModule.deriveOrigin(rawInstanceUrl, allowInsecure) val api = network.api(apiBaseUrl) diff --git a/app/src/main/java/com/pinakes/app/data/store/SessionStore.kt b/app/src/main/java/com/pinakes/app/data/store/SessionStore.kt index b7b8b3a..f73cb7a 100644 --- a/app/src/main/java/com/pinakes/app/data/store/SessionStore.kt +++ b/app/src/main/java/com/pinakes/app/data/store/SessionStore.kt @@ -48,6 +48,16 @@ class SessionStore(context: Context) { /** Whether the committed instance was accepted over insecure (plain HTTP) transport. */ val allowInsecureHttp: Boolean get() = prefs.getBoolean(KEY_ALLOW_INSECURE, false) + /** + * Transient "insecure HTTP allowed" flag for the ONBOARDING probe, before an instance is + * committed (so [allowInsecureHttp] isn't persisted yet). Set by AuthRepository.discover() + * from the onboarding toggle; read by the cleartext gate so the discovery request to a + * plain-HTTP host isn't blocked. In-memory only — a fresh process re-derives it from the + * next discover() call. The persisted flag takes over once the instance is committed. + */ + @Volatile + var pendingAllowInsecureHttp: Boolean = false + /** A stable per-install device id used in the login request. Generated once. */ val deviceId: String get() = prefs.getString(KEY_DEVICE_ID, null) ?: UUID.randomUUID().toString().also { @@ -88,6 +98,7 @@ class SessionStore(context: Context) { fun clearAll() { val savedDevice = deviceId prefs.edit().clear().putString(KEY_DEVICE_ID, savedDevice).apply() + pendingAllowInsecureHttp = false _authState.value = readAuthState() }