From 2db78c429ea88562321608ce8acc70e92da823b2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 27 Mar 2026 17:12:04 +0100 Subject: [PATCH] Fix HTTP basic auth credentials being stripped from instance URLs Preserve user:pass credentials in instance URLs so Invidious instances behind nginx reverse proxies with HTTP basic auth work correctly (#926). Add displayURL property to mask credentials in the UI. --- Yattee/Models/Instance.swift | 15 +++++++++---- Yattee/Views/Settings/EditSourceView.swift | 2 +- YatteeTests/ModelTests.swift | 26 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Yattee/Models/Instance.swift b/Yattee/Models/Instance.swift index 4cb75cd0..90ebfa96 100644 --- a/Yattee/Models/Instance.swift +++ b/Yattee/Models/Instance.swift @@ -119,6 +119,17 @@ struct Instance: Identifiable, Codable, Hashable, Sendable { name ?? url.host ?? url.absoluteString } + /// Returns the URL string with embedded credentials stripped for safe display in the UI. + var displayURL: String { + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false), + components.user != nil else { + return url.absoluteString + } + components.user = nil + components.password = nil + return components.url?.absoluteString ?? url.absoluteString + } + var contentSource: ContentSource { type.contentSource(for: url) } @@ -268,10 +279,6 @@ extension Instance { components.path = String(components.path.dropLast()) } - // Strip embedded credentials (security best practice) - components.user = nil - components.password = nil - return components.url } } diff --git a/Yattee/Views/Settings/EditSourceView.swift b/Yattee/Views/Settings/EditSourceView.swift index 672caa4b..ddde68f6 100644 --- a/Yattee/Views/Settings/EditSourceView.swift +++ b/Yattee/Views/Settings/EditSourceView.swift @@ -124,7 +124,7 @@ private struct EditRemoteServerContent: View { Form { Section { LabeledContent(String(localized: "sources.field.type"), value: instance.type.displayName) - LabeledContent(String(localized: "sources.field.url"), value: instance.url.absoluteString) + LabeledContent(String(localized: "sources.field.url"), value: instance.displayURL) } Section { diff --git a/YatteeTests/ModelTests.swift b/YatteeTests/ModelTests.swift index d0ef75da..96802682 100644 --- a/YatteeTests/ModelTests.swift +++ b/YatteeTests/ModelTests.swift @@ -248,6 +248,32 @@ struct InstanceTests { #expect(simpleHost?.scheme == "https") } + @Test("Instance URL normalization preserves embedded credentials") + func normalizeSourceURLPreservesCredentials() { + let url = Instance.normalizeSourceURL("https://user:pass@server.com") + #expect(url?.user == "user") + #expect(url?.password == "pass") + #expect(url?.host == "server.com") + } + + @Test("Instance displayURL strips credentials") + func displayURLStripsCredentials() { + let instance = Instance( + type: .invidious, + url: URL(string: "https://user:pass@server.com")! + ) + #expect(instance.displayURL == "https://server.com") + } + + @Test("Instance displayURL returns absoluteString when no credentials") + func displayURLWithoutCredentials() { + let instance = Instance( + type: .invidious, + url: URL(string: "https://server.com")! + ) + #expect(instance.displayURL == "https://server.com") + } + @Test("Instance display name uses custom name if set") func displayNameCustom() { let instance = Instance(