mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Merge pull request #762 from stonerl/allow-username-and-password-in-url
Invidious: propper HTTP basic auth support
This commit is contained in:
commit
9a650b4ac0
@ -10,11 +10,28 @@ struct AccountsBridge: Defaults.Bridge {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the urlString to check for embedded username and password
|
||||||
|
var sanitizedUrlString = value.urlString
|
||||||
|
if var urlComponents = URLComponents(string: value.urlString) {
|
||||||
|
if let user = urlComponents.user, let password = urlComponents.password {
|
||||||
|
// Sanitize the embedded username and password
|
||||||
|
let sanitizedUser = user.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed) ?? user
|
||||||
|
let sanitizedPassword = password.addingPercentEncoding(withAllowedCharacters: .urlPasswordAllowed) ?? password
|
||||||
|
|
||||||
|
// Update the URL components with sanitized credentials
|
||||||
|
urlComponents.user = sanitizedUser
|
||||||
|
urlComponents.password = sanitizedPassword
|
||||||
|
|
||||||
|
// Reconstruct the sanitized URL
|
||||||
|
sanitizedUrlString = urlComponents.string ?? value.urlString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"id": value.id,
|
"id": value.id,
|
||||||
"instanceID": value.instanceID ?? "",
|
"instanceID": value.instanceID ?? "",
|
||||||
"name": value.name,
|
"name": value.name,
|
||||||
"apiURL": value.urlString,
|
"apiURL": sanitizedUrlString,
|
||||||
"username": value.username,
|
"username": value.username,
|
||||||
"password": value.password ?? ""
|
"password": value.password ?? ""
|
||||||
]
|
]
|
||||||
|
@ -247,27 +247,27 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func feed(_ page: Int?) -> Resource? {
|
func feed(_ page: Int?) -> Resource? {
|
||||||
resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
|
resourceWithAuthCheck(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
|
||||||
.withParam("page", String(page ?? 1))
|
.withParam("page", String(page ?? 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
var feed: Resource? {
|
var feed: Resource? {
|
||||||
resource(baseURL: account.url, path: basePathAppending("auth/feed"))
|
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/feed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscriptions: Resource? {
|
var subscriptions: Resource? {
|
||||||
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
|
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
|
||||||
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
||||||
.child(channelID)
|
.child(channelID)
|
||||||
.request(.post)
|
.request(.post)
|
||||||
.onCompletion { _ in onCompletion() }
|
.onCompletion { _ in onCompletion() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
|
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
|
||||||
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
|
||||||
.child(channelID)
|
.child(channelID)
|
||||||
.request(.delete)
|
.request(.delete)
|
||||||
.onCompletion { _ in onCompletion() }
|
.onCompletion { _ in onCompletion() }
|
||||||
@ -308,11 +308,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
|
return resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func playlist(_ id: String) -> Resource? {
|
func playlist(_ id: String) -> Resource? {
|
||||||
resource(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
|
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func playlistVideos(_ id: String) -> Resource? {
|
func playlistVideos(_ id: String) -> Resource? {
|
||||||
@ -445,6 +445,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
|
|
||||||
urlComponents.scheme = instanceURLComponents.scheme
|
urlComponents.scheme = instanceURLComponents.scheme
|
||||||
urlComponents.host = instanceURLComponents.host
|
urlComponents.host = instanceURLComponents.host
|
||||||
|
urlComponents.user = instanceURLComponents.user
|
||||||
|
urlComponents.password = instanceURLComponents.password
|
||||||
|
urlComponents.port = instanceURLComponents.port
|
||||||
|
|
||||||
guard let url = urlComponents.url else {
|
guard let url = urlComponents.url else {
|
||||||
return nil
|
return nil
|
||||||
@ -553,6 +556,30 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines if the request requires Basic Auth credentials to be removed
|
||||||
|
private func needsBasicAuthRemoval(for path: String) -> Bool {
|
||||||
|
return path.hasPrefix("\(Self.basePath)/auth/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a resource URL with consideration for removing Basic Auth credentials
|
||||||
|
private func createResourceURL(baseURL: URL, path: String) -> URL {
|
||||||
|
var resourceURL = baseURL
|
||||||
|
|
||||||
|
// Remove Basic Auth credentials if required
|
||||||
|
if needsBasicAuthRemoval(for: path), var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) {
|
||||||
|
urlComponents.user = nil
|
||||||
|
urlComponents.password = nil
|
||||||
|
resourceURL = urlComponents.url ?? baseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceURL.appendingPathComponent(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceWithAuthCheck(baseURL: URL, path: String) -> Resource {
|
||||||
|
let sanitizedURL = createResourceURL(baseURL: baseURL, path: path)
|
||||||
|
return super.resource(absoluteURL: sanitizedURL)
|
||||||
|
}
|
||||||
|
|
||||||
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
|
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
|
||||||
details["videoThumbnails"].arrayValue.compactMap { json in
|
details["videoThumbnails"].arrayValue.compactMap { json in
|
||||||
guard let url = json["url"].url,
|
guard let url = json["url"].url,
|
||||||
@ -563,13 +590,20 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// some of instances are not configured properly and return thumbnails links
|
// Some instances are not configured properly and return thumbnail links
|
||||||
// with incorrect scheme
|
// with an incorrect scheme or a missing port.
|
||||||
components.scheme = accountUrlComponents.scheme
|
components.scheme = accountUrlComponents.scheme
|
||||||
|
components.port = accountUrlComponents.port
|
||||||
|
|
||||||
|
// If basic HTTP authentication is used,
|
||||||
|
// the username and password need to be prepended to the URL.
|
||||||
|
components.user = accountUrlComponents.user
|
||||||
|
components.password = accountUrlComponents.password
|
||||||
|
|
||||||
guard let thumbnailUrl = components.url else {
|
guard let thumbnailUrl = components.url else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
print("Final thumbnail URL: \(thumbnailUrl)")
|
||||||
|
|
||||||
return Thumbnail(url: thumbnailUrl, quality: .init(rawValue: quality)!)
|
return Thumbnail(url: thumbnailUrl, quality: .init(rawValue: quality)!)
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ struct InstanceForm: View {
|
|||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.keyboardType(.URL)
|
.keyboardType(.URL)
|
||||||
#endif
|
#endif
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -66,6 +66,11 @@
|
|||||||
value = "Yes"
|
value = "Yes"
|
||||||
isEnabled = "YES">
|
isEnabled = "YES">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "IDELogRedirectionPolicy"
|
||||||
|
value = "oslogToStdio"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
</EnvironmentVariables>
|
</EnvironmentVariables>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
|
Loading…
Reference in New Issue
Block a user