fall back to password auth when the server rejects the token scheme

Navidrome refuses the Subsonic token scheme for LDAP users with error code 41. Catch it
once in SubsonicClient: flip that client to the password scheme, tell SourceRepository to
persist the change for the source and show a toast, then retry the request. Only 41 is
retried, the flip is idempotent, and a client never retries more than once.
This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent 90171468cc
commit 59706f28c1
5 changed files with 124 additions and 11 deletions
@@ -25,14 +25,22 @@ class SubsonicClientTest {
server.shutdown()
}
private fun client(tokenAuth: Boolean) =
SubsonicClient(
baseUrl = server.url("/"),
username = "guest",
password = "secret",
useTokenAuth = tokenAuth,
http = OkHttpClient(),
)
private fun client(
tokenAuth: Boolean,
onTokenAuthUnsupported: (() -> Unit)? = null,
) = SubsonicClient(
baseUrl = server.url("/"),
username = "guest",
password = "secret",
useTokenAuth = tokenAuth,
http = OkHttpClient(),
onTokenAuthUnsupported = onTokenAuthUnsupported,
)
private fun failed(
code: Int,
message: String,
) = "<subsonic-response status=\"failed\"><error code=\"$code\" message=\"$message\"/></subsonic-response>"
@Test
fun tokenAuthSendsSaltAndHash() {
@@ -90,4 +98,51 @@ class SubsonicClientTest {
assertThrows(SubsonicException::class.java) { client(tokenAuth = true).check("ping") }
}
@Test
fun tokenAuthUnsupportedRetriesWithPassword() {
server.enqueue(MockResponse().setBody(failed(41, "Token authentication not supported for LDAP users.")))
server.enqueue(MockResponse().setBody("<subsonic-response status=\"ok\" version=\"1.16.1\"/>"))
var notified = 0
client(tokenAuth = true, onTokenAuthUnsupported = { notified++ }).check("ping")
val first = server.takeRequest().requestUrl!!
assertNotNull(first.queryParameter("t"))
assertNull(first.queryParameter("p"))
val second = server.takeRequest().requestUrl!!
assertNull(second.queryParameter("t"))
assertEquals("secret", second.queryParameter("p"))
assertEquals(1, notified)
}
@Test
fun tokenAuthUnsupportedIsNotifiedOnceAndNotRetriedAgain() {
server.enqueue(MockResponse().setBody(failed(41, "Token authentication not supported for LDAP users.")))
server.enqueue(MockResponse().setBody("<subsonic-response status=\"ok\" version=\"1.16.1\"/>"))
server.enqueue(MockResponse().setBody(failed(41, "Token authentication not supported for LDAP users.")))
var notified = 0
val client = client(tokenAuth = true, onTokenAuthUnsupported = { notified++ })
client.check("ping")
assertThrows(SubsonicException::class.java) { client.check("ping") }
assertEquals(1, notified)
assertEquals(3, server.requestCount)
}
@Test
fun otherErrorsAreNotRetried() {
server.enqueue(MockResponse().setBody(failed(40, "Wrong username")))
var notified = 0
val error =
assertThrows(SubsonicException::class.java) {
client(tokenAuth = true, onTokenAuthUnsupported = { notified++ }).check("ping")
}
assertEquals(40, error.code)
assertEquals(0, notified)
assertEquals(1, server.requestCount)
}
}