Fix three basic-auth regressions surfaced by end-to-end testing

- InstanceDetector: a single 401 from one probe was over-eagerly concluded
  as "credentials invalid" / "credentials required". On instances behind a
  reverse proxy where one probe path (e.g. Yattee Server's /info) hits a
  same-origin redirect, iOS URLSession strips the Authorization header on
  the redirect and the request 401s even with valid credentials. Track 401s
  across all probes and only conclude basicAuthRequired/basicAuthInvalid
  when no probe matched and at least one returned 401.

- InstanceLoginView: the Invidious/Piped login flow constructed an API
  client backed by the shared appEnvironment.httpClient, which has no
  per-instance basic-auth headers. For instances behind a reverse proxy,
  the login POST 401d before reaching the upstream login endpoint. Build a
  per-instance HTTPClient with the basic-auth Authorization header baked in
  via setDefaultHeaders, mirroring ContentService.httpClientWithBasicAuth.

- InvidiousAPI.login: the login function constructs its own URLSession (to
  capture Set-Cookie via a redirect-blocking delegate), so it never
  inherits headers from the injected httpClient. Add an optional
  extraHeaders parameter and have InstanceLoginView pass the basic-auth
  header through when present. PipedAPI.login uses httpClient.fetch and
  inherits defaultHeaders correctly, so no change is needed there.
This commit is contained in:
Arkadiusz Fal
2026-04-06 22:11:55 +02:00
parent 3dd4073db7
commit eefd49f743
3 changed files with 54 additions and 12 deletions

View File

@@ -94,11 +94,17 @@ actor InstanceDetector {
basicAuthHeader: String? = nil
) async -> Result<InstanceDetectionResult, DetectionError> {
let extraHeaders: [String: String]? = basicAuthHeader.map { ["Authorization": $0] }
// If we already supplied credentials and still get 401, those credentials are wrong.
let unauthorizedError: DetectionError = basicAuthHeader == nil ? .basicAuthRequired : .basicAuthInvalid
// Try each detection method in order of specificity
// Check Yattee Server first as it's most specific
// A 401 from a single probe is *not* enough to conclude that credentials are
// invalid. Some probe paths (e.g. Yattee Server's `/info`) trigger an HTTP
// redirect on Invidious, and iOS URLSession strips the Authorization header
// when following redirects, so the redirected request 401s even when the
// credentials are valid. We instead consider credentials bad only if EVERY
// probe failed with 401 and none matched.
var sawUnauthorized = false
// Try each detection method in order of specificity.
// Check Yattee Server first as it's most specific.
do {
if let result = try await detectYatteeServerWithError(url: url, extraHeaders: extraHeaders) {
return .success(result)
@@ -106,7 +112,7 @@ actor InstanceDetector {
} catch let error as DetectionError {
return .failure(error)
} catch APIError.unauthorized {
return .failure(unauthorizedError)
sawUnauthorized = true
} catch {
// Continue to next detection method
}
@@ -116,7 +122,7 @@ actor InstanceDetector {
return .success(InstanceDetectionResult(type: .peertube))
}
} catch APIError.unauthorized {
return .failure(unauthorizedError)
sawUnauthorized = true
} catch {
// Continue to next detection method
}
@@ -126,7 +132,7 @@ actor InstanceDetector {
return .success(InstanceDetectionResult(type: .invidious))
}
} catch APIError.unauthorized {
return .failure(unauthorizedError)
sawUnauthorized = true
} catch {
// Continue to next detection method
}
@@ -136,11 +142,17 @@ actor InstanceDetector {
return .success(InstanceDetectionResult(type: .piped))
}
} catch APIError.unauthorized {
return .failure(unauthorizedError)
sawUnauthorized = true
} catch {
// Fall through
}
// No probe matched. If at least one probe returned 401, the instance is
// (or appears to be) behind HTTP Basic Auth. Distinguish "needs creds" from
// "creds rejected" by whether the caller already supplied a header.
if sawUnauthorized {
return .failure(basicAuthHeader == nil ? .basicAuthRequired : .basicAuthInvalid)
}
return .failure(.unknownType)
}