Skip to content

feat(onboarding): opt-in "Allow insecure HTTP" for HTTP-only self-hosted instances (#16)#20

Merged
fabiodalez-dev merged 2 commits into
mainfrom
feat/allow-insecure-http
Jul 6, 2026
Merged

feat(onboarding): opt-in "Allow insecure HTTP" for HTTP-only self-hosted instances (#16)#20
fabiodalez-dev merged 2 commits into
mainfrom
feat/allow-insecure-http

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Fixes #16.

Problem

A self-hoster on plain HTTP (Uwe's QNAP/Docker instance, no TLS) can't connect: the app requires HTTPS for any non-loopback host, so a bare host is upgraded to https:// (which the server doesn't answer) and an explicit http:// URL is blocked by Android's cleartext policy — both surface as the generic "Couldn't reach that address", even though the same instance opens fine in a browser on the same phone. So it looks like a networking/Docker problem when it's really the HTTPS-only enforcement.

Change — explicit, off-by-default opt-in

Lets those users connect at their own risk without weakening the default for everyone:

  • network_security_config: a static config can't whitelist a host entered at runtime, so cleartext is permitted at the OS level and the app becomes the sole gatekeeper (documented in the file).
  • NetworkModule deriveApiBaseUrl() / isTransportAllowed() take an allowInsecure flag — when set, a bare host is prefixed http:// and any http:// host is accepted; otherwise the existing "HTTPS required except loopback" rule is untouched.
  • SessionStore persists a per-instance allow_insecure_http flag; AuthRepository threads it through discover() / commitInstance().
  • Onboarding shows an "Allow insecure HTTP" switch (off by default) with a clear "connection won't be encrypted — use at your own risk" hint, in all 4 locales (en/it/de/fr).

Without the toggle, the app refuses plain-HTTP remote instances exactly as before.

Tests

NetworkUrlTest gains coverage for the opt-in path (bare host → http://, explicit https:// still wins, remote http:// accepted only with the flag) plus a regression guard that the default still rejects remote HTTP. Verified: compileDebugKotlin + testDebugUnitTest (NetworkUrlTest) pass.

Note on the security trade-off

Cleartext is now permitted at the OS layer, so the app — not the manifest — enforces transport policy. This is the standard pattern for apps that let users add arbitrary self-hosted instances (Nextcloud/Jellyfin do the same). A cleartext request is only ever issued for loopback/dev hosts or an instance the user knowingly accepted as insecure.

Summary by CodeRabbit

  • New Features

    • Aggiunta un’opzione in onboarding per consentire istanze con HTTP non sicuro.
    • La scelta viene salvata e mantenuta tra le sessioni.
    • Aggiornata la gestione degli indirizzi per supportare input senza schema.
  • Bug Fixes

    • Migliorata la compatibilità con istanze self-hosted non HTTPS, mantenendo comunque il comportamento sicuro predefinito.
  • Documentation

    • Aggiornati i testi informativi e gli avvisi nelle lingue supportate.

…ted instances (#16)

Self-hosters who run Pinakes over plain HTTP (e.g. a QNAP/Docker instance with no
TLS) couldn't connect: the app requires HTTPS for any non-loopback host, so a bare
host is upgraded to https:// (which the server doesn't answer) and an explicit
http:// URL is blocked by the OS cleartext policy — surfacing as the generic
"Couldn't reach that address" while the same instance opens fine in a browser.

Adds an explicit, off-by-default opt-in so those users can connect at their own risk
without weakening the default for everyone:

- network_security_config: a static config can't whitelist a runtime-entered host,
  so cleartext is permitted at the OS level and the app becomes the sole gatekeeper.
- NetworkModule.deriveApiBaseUrl()/isTransportAllowed() take an allowInsecure flag:
  when set, a bare host is prefixed http:// and any http:// host is accepted;
  otherwise the existing "HTTPS required except loopback" rule is unchanged.
- SessionStore persists the per-instance allow_insecure_http flag; AuthRepository
  threads it through discover()/commitInstance().
- Onboarding shows an "Allow insecure HTTP" switch (off by default) with a clear
  "connection won't be encrypted — use at your own risk" hint, in all 4 locales.

Without the toggle the app still refuses plain-HTTP remote instances exactly as
before. Unit tests cover both the opt-in path and the default-rejects-remote-http
regression. Verified: compileDebugKotlin + NetworkUrlTest pass.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fabiodalez-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5b94b41-79fe-4e9e-bd30-e8a1ac311807

📥 Commits

Reviewing files that changed from the base of the PR and between 90df8c0 and bb86208.

📒 Files selected for processing (5)
  • app/src/main/java/com/pinakes/app/PinakesApplication.kt
  • app/src/main/java/com/pinakes/app/data/network/CleartextGuard.kt
  • app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt
  • app/src/main/java/com/pinakes/app/ui/screens/detail/PdfReaderDialog.kt
  • app/src/main/res/xml/network_security_config.xml

Walkthrough

Introdotto un flag opt-in allowInsecure per consentire connessioni HTTP non cifrate verso istanze self-hosted senza HTTPS. Il flag attraversa NetworkModule, AuthRepository, SessionStore fino all'onboarding UI con uno Switch dedicato, aggiornata la network security config e aggiunte traduzioni in quattro lingue.

Changes

Opt-in HTTP non sicuro

Layer / File(s) Summary
Logica core di derivazione URL
app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt
deriveApiBaseUrl, deriveOrigin e isTransportAllowed accettano un nuovo parametro allowInsecure (default false) che permette scheme http:// esplicito e trasporto verso host remoti non loopback.
Discovery e persistenza sessione
app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt, app/src/main/java/com/pinakes/app/data/store/SessionStore.kt
AuthRepository.discover propaga allowInsecure, HealthDiscovery include il nuovo campo, commitInstance lo passa a SessionStore.saveInstance che persiste e legge la preferenza allowInsecureHttp.
Stato e UI onboarding
app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt, app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingScreen.kt
OnboardingUiState aggiunge allowInsecureHttp, onAllowInsecureChange aggiorna lo stato e resetta errori/discovery, la UI mostra uno Switch collegato all'handler.
Config rete, test e traduzioni
app/src/main/res/xml/network_security_config.xml, app/src/test/java/com/pinakes/app/NetworkUrlTest.kt, i18n/de.json, i18n/en.json, i18n/fr.json, i18n/it.json
cleartextTrafficPermitted passa a true a livello OS demandando l'enforcement HTTPS all'app, aggiunti test unitari per i nuovi comportamenti e stringhe onboarding_allow_http_label/onboarding_allow_http_hint in quattro lingue.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OnboardingScreen
  participant OnboardingViewModel
  participant AuthRepository
  participant NetworkModule
  participant SessionStore

  OnboardingScreen->>OnboardingViewModel: onAllowInsecureChange(true)
  OnboardingViewModel->>OnboardingViewModel: aggiorna allowInsecureHttp, resetta errori/discovery
  OnboardingScreen->>OnboardingViewModel: avvia discovery
  OnboardingViewModel->>AuthRepository: discover(url, allowInsecureHttp)
  AuthRepository->>NetworkModule: deriveApiBaseUrl(url, allowInsecure)
  AuthRepository->>NetworkModule: isTransportAllowed(apiBaseUrl, allowInsecure)
  NetworkModule-->>AuthRepository: apiBaseUrl, transportAllowed
  AuthRepository-->>OnboardingViewModel: HealthDiscovery(allowInsecure, transportAllowed)
  OnboardingViewModel->>AuthRepository: commitInstance(discovery)
  AuthRepository->>SessionStore: saveInstance(origin, apiBaseUrl, libraryName, allowInsecure)
Loading

Poem

Un coniglio salta sopra il muro sicuro,
"http" bisbiglia, senza esser oscuro. 🐇
Con uno switch acceso, il rischio è dichiarato,
l'istanza self-hosted finalmente è raggiungibile.
Salto tra le config, senza più paura! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo descrive chiaramente l’opt-in per HTTP non sicuro su istanze self-hosted, che è il cambiamento principale della PR.
Linked Issues check ✅ Passed La PR risolve il problema #16 permettendo la connessione a istanze self-hosted HTTP e propagando l’opzione nell’onboarding e nel commit.
Out of Scope Changes check ✅ Passed Non emergono modifiche estranee: UI, persistenza, networking, test e traduzioni supportano tutti l’opt-in HTTP.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/allow-insecure-http

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingScreen.kt (1)

109-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rendere l'intera riga interattiva per l'accessibilità.

I due Text (label e hint) non sono associati semanticamente allo Switch, e l'area di tocco è limitata al solo switch. Spostando l'azione su Modifier.toggleable della Row con role = Role.Switch e impostando onCheckedChange = null sullo Switch, l'etichetta viene annunciata insieme allo stato dal lettore schermo e l'intera riga diventa toccabile.

♻️ Refactor proposto per l'accessibilità
                 Row(
-                    modifier = form,
+                    modifier = form.toggleable(
+                        value = state.allowInsecureHttp,
+                        role = Role.Switch,
+                        onValueChange = vm::onAllowInsecureChange,
+                    ),
                     verticalAlignment = Alignment.CenterVertically,
                 ) {
                     Column(Modifier.weight(1f)) {
                         Text(
                             stringResource(R.string.onboarding_allow_http_label),
                             style = MaterialTheme.typography.bodyMedium,
                             color = MaterialTheme.colorScheme.onSurface,
                         )
                         Text(
                             stringResource(R.string.onboarding_allow_http_hint),
                             style = MaterialTheme.typography.bodySmall,
                             color = MaterialTheme.colorScheme.onSurfaceVariant,
                         )
                     }
                     Spacer(Modifier.width(Spacing.md))
                     Switch(
                         checked = state.allowInsecureHttp,
-                        onCheckedChange = vm::onAllowInsecureChange,
+                        onCheckedChange = null,
                     )
                 }

Richiede gli import androidx.compose.foundation.selection.toggleable e androidx.compose.ui.semantics.Role.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingScreen.kt`
around lines 109 - 130, Make the entire onboarding HTTP setting row accessible
by moving the toggle interaction from the Switch to the parent Row in
OnboardingScreen. Apply Modifier.toggleable with role = Role.Switch to the Row
that wraps the label/hint and switch, so the full row is clickable and the text
is announced with the state. Keep the existing state and handler wiring from
state.allowInsecureHttp and vm::onAllowInsecureChange, and set onCheckedChange
to null on the Switch so it only reflects the Row’s toggle behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/res/xml/network_security_config.xml`:
- Around line 3-21: The permissive cleartext setting in network_security_config
is too broad because traffic can still bypass NetworkModule.isTransportAllowed()
via other loaders like Coil coverUrl and PdfReaderDialog downloads. Tighten the
network-security-config so cleartext is only allowed for loopback/dev hosts, or
route those non-NetworkModule requests through the same transport check used by
NetworkModule and OnboardingViewModel/SessionStore.allowInsecureHttp. Keep the
fix focused on the base-config and the related request paths that currently
bypass the app-level gate.

---

Nitpick comments:
In `@app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingScreen.kt`:
- Around line 109-130: Make the entire onboarding HTTP setting row accessible by
moving the toggle interaction from the Switch to the parent Row in
OnboardingScreen. Apply Modifier.toggleable with role = Role.Switch to the Row
that wraps the label/hint and switch, so the full row is clickable and the text
is announced with the state. Keep the existing state and handler wiring from
state.allowInsecureHttp and vm::onAllowInsecureChange, and set onCheckedChange
to null on the Switch so it only reflects the Row’s toggle behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0673014d-4107-4a56-a8ac-f61279a0d7cf

📥 Commits

Reviewing files that changed from the base of the PR and between 9ddc37f and 90df8c0.

📒 Files selected for processing (11)
  • app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt
  • app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt
  • app/src/main/java/com/pinakes/app/data/store/SessionStore.kt
  • app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt
  • app/src/main/res/xml/network_security_config.xml
  • app/src/test/java/com/pinakes/app/NetworkUrlTest.kt
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json

Comment thread app/src/main/res/xml/network_security_config.xml
…o (CodeRabbit)

The opt-in flipped the network-security-config base to cleartextTrafficPermitted=true,
which alone would let ANY loader do plain HTTP — not just the API client that goes
through NetworkModule.isTransportAllowed(). Coil (book covers) and the ebook PDF
download each build their own OkHttpClient and bypassed that gate, so on an HTTPS
instance an http:// cover/PDF URL could have downgraded silently.

Add a shared CleartextGuardInterceptor (blocks http:// to a non-loopback host unless
SessionStore.allowInsecureHttp) and wire it into all three OkHttp clients: NetworkModule,
the Coil ImageLoader (PinakesApplication, via a Hilt EntryPoint) and PdfReaderDialog's
downloader. The app is now the real universal gate; cleartext is only ever issued for
loopback/dev hosts or an instance the user knowingly accepted as insecure.

Verified: compileDebugKotlin passes.
@fabiodalez-dev fabiodalez-dev merged commit 1f2c9d8 into main Jul 6, 2026
1 of 2 checks passed
@fabiodalez-dev fabiodalez-dev deleted the feat/allow-insecure-http branch July 6, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

android mobile app

1 participant