Inject basic auth via per-instance HTTPClient default headers

Replace the YatteeServerAPI setAuthHeader/buildHeaders pattern (which was
race-prone on the shared actor across multiple instances) with a generic
mechanism: HTTPClient now supports a defaultHeaders dictionary applied to
every request, and ContentService builds a per-instance HTTPClient with the
basic-auth Authorization header baked in whenever credentials are configured.

The same code path now works uniformly for Invidious, Piped, PeerTube, and
Yattee Server, so any instance sitting behind a reverse proxy that requires
HTTP Basic Auth can be authenticated regardless of backend type. Cached
default API actors are still reused when no basic-auth header is needed.
This commit is contained in:
Arkadiusz Fal
2026-04-06 19:53:47 +02:00
parent aed78c13fb
commit 63f1cb1f25
6 changed files with 103 additions and 101 deletions

View File

@@ -16,6 +16,12 @@ actor HTTPClient {
private var userAgent: String?
private var randomizeUserAgentPerRequest: Bool = false
/// Headers automatically applied to every request from this client.
/// Per-call `customHeaders` override these on key conflict.
/// Used to bake an HTTP Basic Auth `Authorization` header into a per-instance client
/// when the instance sits behind a reverse proxy that requires basic auth.
private var defaultHeaders: [String: String] = [:]
// MARK: - Initialization
init(session: URLSession = .shared, decoder: JSONDecoder = .init()) {
@@ -41,6 +47,12 @@ actor HTTPClient {
self.randomizeUserAgentPerRequest = enabled
}
/// Sets headers to be applied to every request from this client.
/// Per-call `customHeaders` override these on key conflict.
func setDefaultHeaders(_ headers: [String: String]) {
self.defaultHeaders = headers
}
// MARK: - Public Methods
/// Fetches and decodes a response from the given endpoint.
@@ -104,7 +116,13 @@ actor HTTPClient {
mutableRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent")
}
// Apply custom headers (e.g., X-API-Key for authenticated requests)
// Apply default headers first (e.g., basic auth bound to a per-instance client)
for (key, value) in defaultHeaders {
mutableRequest.setValue(value, forHTTPHeaderField: key)
}
// Apply custom headers (e.g., X-API-Key for authenticated requests).
// These override default headers on key conflict.
if let customHeaders {
for (key, value) in customHeaders {
mutableRequest.setValue(value, forHTTPHeaderField: key)