sync: only report the address we actually replaced

Four corrections to the login-flow work earlier on this branch, all
found in review.

The mismatch was computed by comparing hosts exactly while
reachableOrigin substitutes only across registrable domains. A server
answering nc.example.com for a poll endpoint on cloud.example.com is
used verbatim and correctly -- and we told the user we had replaced it
because it was unreachable, blaming a setting that was right. The
decision now has a name and says what it means: report a mismatch only
when the address we used is not the one that was claimed.

baseOf matched only the index.php spelling of the poll path, but
Nextcloud drops index.php from generated routes when
htaccess.IgnoreFrontController is on -- so a subdirectory install
answering /nc/login/v2/poll rebuilt the root as / and lost the prefix,
which is the loss that function exists to prevent.

browserFailed built a fresh step and dropped the mismatch note, which
is often the explanation for the failure it is replacing it with. And
the note is sticky by design, but it belongs to the address that
produced it: starting a new attempt now clears it, rather than carrying
a claim about one server's configuration into another's.
This commit is contained in:
2026-09-07 23:28:08 +02:00
parent 6aaa15b130
commit 41ffc75fc5
3 changed files with 74 additions and 7 deletions
@@ -138,6 +138,10 @@ class AddAccountViewModel @Inject constructor(
// *Continue*, not start over — so a second attempt would mint a second
// password on top of the first without this.
discardMintedPassword()
// The mismatch is sticky on purpose, but it belongs to the address that
// produced it — carrying it into a different server's flow makes a claim
// about that server's settings which was never measured.
_state.update { it.copy(originMismatch = null) }
typedInput = input
val quirk = ServerQuirk.forInput(input)
@@ -521,7 +525,12 @@ class AddAccountViewModel @Inject constructor(
}
private fun browserFailed(reason: AddAccountMessage) = _state.update {
it.copy(step = AddAccountStep.WaitingForBrowser(error = reason), openInBrowser = null)
// Keeps whatever the step was carrying. The host-mismatch note is often
// the *explanation* for the failure, so dropping it removes the warning
// exactly when it becomes worth reading.
val step = it.step as? AddAccountStep.WaitingForBrowser
?: AddAccountStep.WaitingForBrowser()
it.copy(step = step.copy(error = reason), openInBrowser = null)
}
private fun updateCredentials(transform: (AddAccountStep.EnterCredentials) -> AddAccountStep.EnterCredentials) {