Generalize Yattee Server credentials manager to BasicAuthCredentialsManager

Renames YatteeServerCredentialsManager → BasicAuthCredentialsManager so the
same Keychain-backed username/password storage can be reused for any instance
type that sits behind a reverse proxy requiring HTTP Basic Auth. Adds a
one-time migration that moves existing items from the legacy
'com.yattee.yatteeserver' Keychain service to 'com.yattee.basicauth',
preserving the iCloud-sync attribute. No behavior change for end users.
This commit is contained in:
Arkadiusz Fal
2026-04-06 19:45:16 +02:00
parent 240cf23693
commit 8cd3aca96c
12 changed files with 141 additions and 56 deletions

View File

@@ -47,7 +47,7 @@ final class AppEnvironment {
let handoffManager: HandoffManager let handoffManager: HandoffManager
let invidiousCredentialsManager: InvidiousCredentialsManager let invidiousCredentialsManager: InvidiousCredentialsManager
let pipedCredentialsManager: PipedCredentialsManager let pipedCredentialsManager: PipedCredentialsManager
let yatteeServerCredentialsManager: YatteeServerCredentialsManager let basicAuthCredentialsManager: BasicAuthCredentialsManager
let homeInstanceCache: HomeInstanceCache let homeInstanceCache: HomeInstanceCache
let invidiousAPI: InvidiousAPI let invidiousAPI: InvidiousAPI
let pipedAPI: PipedAPI let pipedAPI: PipedAPI
@@ -82,12 +82,12 @@ final class AppEnvironment {
instances.setSettingsManager(settings) instances.setSettingsManager(settings)
self.instancesManager = instances self.instancesManager = instances
// Initialize Yattee Server Credentials Manager early (needed for ContentService) // Initialize Basic Auth Credentials Manager early (needed for ContentService)
let yatteeServerCreds = YatteeServerCredentialsManager() let basicAuthCreds = BasicAuthCredentialsManager()
yatteeServerCreds.settingsManager = settings basicAuthCreds.settingsManager = settings
self.yatteeServerCredentialsManager = yatteeServerCreds self.basicAuthCredentialsManager = basicAuthCreds
let contentSvc = ContentService(httpClient: client, yatteeServerCredentialsManager: yatteeServerCreds) let contentSvc = ContentService(httpClient: client, basicAuthCredentialsManager: basicAuthCreds)
self.contentService = contentSvc self.contentService = contentSvc
self.instanceDetector = InstanceDetector(httpClient: client) self.instanceDetector = InstanceDetector(httpClient: client)
self.navigationCoordinator = navigationCoordinator ?? NavigationCoordinator() self.navigationCoordinator = navigationCoordinator ?? NavigationCoordinator()
@@ -374,7 +374,7 @@ final class AppEnvironment {
case .piped: case .piped:
return pipedCredentialsManager return pipedCredentialsManager
case .yatteeServer: case .yatteeServer:
return yatteeServerCredentialsManager return basicAuthCredentialsManager
default: default:
return nil return nil
} }

View File

@@ -41,10 +41,10 @@ actor ContentService: ContentServiceProtocol {
private let defaultPeerTubeAPI: PeerTubeAPI private let defaultPeerTubeAPI: PeerTubeAPI
private let defaultYatteeServerAPI: YatteeServerAPI private let defaultYatteeServerAPI: YatteeServerAPI
/// Credentials manager for fetching Yattee Server auth headers on demand. /// Credentials manager for fetching basic auth headers on demand.
private let yatteeServerCredentialsManager: YatteeServerCredentialsManager? private let basicAuthCredentialsManager: BasicAuthCredentialsManager?
init(httpClient: HTTPClient, yatteeServerCredentialsManager: YatteeServerCredentialsManager? = nil) { init(httpClient: HTTPClient, basicAuthCredentialsManager: BasicAuthCredentialsManager? = nil) {
// Legacy init - create factory internally // Legacy init - create factory internally
self.httpClientFactory = HTTPClientFactory() self.httpClientFactory = HTTPClientFactory()
self.defaultHTTPClient = httpClient self.defaultHTTPClient = httpClient
@@ -52,10 +52,10 @@ actor ContentService: ContentServiceProtocol {
self.defaultPipedAPI = PipedAPI(httpClient: httpClient) self.defaultPipedAPI = PipedAPI(httpClient: httpClient)
self.defaultPeerTubeAPI = PeerTubeAPI(httpClient: httpClient) self.defaultPeerTubeAPI = PeerTubeAPI(httpClient: httpClient)
self.defaultYatteeServerAPI = YatteeServerAPI(httpClient: httpClient) self.defaultYatteeServerAPI = YatteeServerAPI(httpClient: httpClient)
self.yatteeServerCredentialsManager = yatteeServerCredentialsManager self.basicAuthCredentialsManager = basicAuthCredentialsManager
} }
init(httpClientFactory: HTTPClientFactory, yatteeServerCredentialsManager: YatteeServerCredentialsManager? = nil) { init(httpClientFactory: HTTPClientFactory, basicAuthCredentialsManager: BasicAuthCredentialsManager? = nil) {
self.httpClientFactory = httpClientFactory self.httpClientFactory = httpClientFactory
// Create default client for instances that don't need insecure SSL // Create default client for instances that don't need insecure SSL
self.defaultHTTPClient = httpClientFactory.createClient(allowInvalidCertificates: false) self.defaultHTTPClient = httpClientFactory.createClient(allowInvalidCertificates: false)
@@ -63,7 +63,7 @@ actor ContentService: ContentServiceProtocol {
self.defaultPipedAPI = PipedAPI(httpClient: defaultHTTPClient) self.defaultPipedAPI = PipedAPI(httpClient: defaultHTTPClient)
self.defaultPeerTubeAPI = PeerTubeAPI(httpClient: defaultHTTPClient) self.defaultPeerTubeAPI = PeerTubeAPI(httpClient: defaultHTTPClient)
self.defaultYatteeServerAPI = YatteeServerAPI(httpClient: defaultHTTPClient) self.defaultYatteeServerAPI = YatteeServerAPI(httpClient: defaultHTTPClient)
self.yatteeServerCredentialsManager = yatteeServerCredentialsManager self.basicAuthCredentialsManager = basicAuthCredentialsManager
} }
// MARK: - Routing // MARK: - Routing
@@ -114,7 +114,7 @@ actor ContentService: ContentServiceProtocol {
} }
// Fetch auth header directly from credentials manager (avoids race condition on app startup) // Fetch auth header directly from credentials manager (avoids race condition on app startup)
let authHeader = await yatteeServerCredentialsManager?.basicAuthHeader(for: instance) let authHeader = await basicAuthCredentialsManager?.basicAuthHeader(for: instance)
await api.setAuthHeader(authHeader) await api.setAuthHeader(authHeader)
return api return api

View File

@@ -102,7 +102,7 @@ final class BackgroundFeedRefresher {
do { do {
let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient()) let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient())
let authHeader = appEnvironment.yatteeServerCredentialsManager.basicAuthHeader(for: yatteeServer) let authHeader = appEnvironment.basicAuthCredentialsManager.basicAuthHeader(for: yatteeServer)
await yatteeServerAPI.setAuthHeader(authHeader) await yatteeServerAPI.setAuthHeader(authHeader)
let response = try await yatteeServerAPI.postFeed( let response = try await yatteeServerAPI.postFeed(
channels: channelRequests, channels: channelRequests,

View File

@@ -1,24 +1,30 @@
// //
// YatteeServerCredentialsManager.swift // BasicAuthCredentialsManager.swift
// Yattee // Yattee
// //
// Manages Yattee Server credentials (username/password) stored securely in the Keychain. // Manages HTTP Basic Auth credentials (username/password) stored securely in the Keychain.
// Works for any instance type (Invidious, Piped, PeerTube, Yattee Server) used when an
// instance sits behind a reverse proxy that requires HTTP Basic Auth.
// //
import Foundation import Foundation
import Security import Security
/// Credential structure for Yattee Server basic authentication. /// Credential structure for HTTP Basic authentication.
struct YatteeServerCredential: Codable { struct BasicAuthCredential: Codable {
let username: String let username: String
let password: String let password: String
} }
/// Manages Yattee Server credentials stored securely in the Keychain. /// Manages HTTP Basic Auth credentials stored securely in the Keychain.
@MainActor @MainActor
@Observable @Observable
final class YatteeServerCredentialsManager: InstanceCredentialsManager { final class BasicAuthCredentialsManager: InstanceCredentialsManager {
private let keychainServiceName = "com.yattee.yatteeserver" private let keychainServiceName = "com.yattee.basicauth"
/// Legacy Keychain service name from when basic auth was Yattee-Server-only.
/// We migrate items from this service to `keychainServiceName` on init.
private let legacyKeychainServiceName = "com.yattee.yatteeserver"
/// Tracks which instances have stored credentials (for reactive UI updates) /// Tracks which instances have stored credentials (for reactive UI updates)
private(set) var loggedInInstanceIDs: Set<UUID> = [] private(set) var loggedInInstanceIDs: Set<UUID> = []
@@ -31,22 +37,24 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
settingsManager?.iCloudSyncEnabled == true && settingsManager?.syncInstances == true settingsManager?.iCloudSyncEnabled == true && settingsManager?.syncInstances == true
} }
init() {} init() {
migrateLegacyKeychainItems()
}
// MARK: - Public API // MARK: - Public API
/// Stores credentials for a Yattee Server instance. /// Stores credentials for an instance.
/// Credentials sync to iCloud Keychain when iCloud sync is enabled for instances. /// Credentials sync to iCloud Keychain when iCloud sync is enabled for instances.
/// - Parameters: /// - Parameters:
/// - username: The username for basic auth /// - username: The username for basic auth
/// - password: The password for basic auth /// - password: The password for basic auth
/// - instance: The Yattee Server instance /// - instance: The instance
func setCredentials(username: String, password: String, for instance: Instance) { func setCredentials(username: String, password: String, for instance: Instance) {
let account = instance.id.uuidString let account = instance.id.uuidString
let credential = YatteeServerCredential(username: username, password: password) let credential = BasicAuthCredential(username: username, password: password)
guard let data = try? JSONEncoder().encode(credential) else { guard let data = try? JSONEncoder().encode(credential) else {
LoggingService.shared.error("Failed to encode Yattee Server credentials", category: .keychain) LoggingService.shared.error("Failed to encode basic auth credentials", category: .keychain)
return return
} }
@@ -74,7 +82,7 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
if status == errSecSuccess { if status == errSecSuccess {
LoggingService.shared.info( LoggingService.shared.info(
"Stored credentials for Yattee Server instance", "Stored basic auth credentials for instance",
category: .keychain, category: .keychain,
details: "instanceID=\(instance.id), iCloudSync=\(syncEnabled)" details: "instanceID=\(instance.id), iCloudSync=\(syncEnabled)"
) )
@@ -82,18 +90,18 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
loggedInInstanceIDs.insert(instance.id) loggedInInstanceIDs.insert(instance.id)
} else { } else {
LoggingService.shared.error( LoggingService.shared.error(
"Failed to store credentials for Yattee Server instance", "Failed to store basic auth credentials for instance",
category: .keychain, category: .keychain,
details: "instanceID=\(instance.id), status=\(status)" details: "instanceID=\(instance.id), status=\(status)"
) )
} }
} }
/// Retrieves credentials for a Yattee Server instance. /// Retrieves credentials for an instance.
/// Searches both synced and non-synced items. /// Searches both synced and non-synced items.
/// - Parameter instance: The Yattee Server instance /// - Parameter instance: The instance
/// - Returns: The credentials if stored, nil otherwise /// - Returns: The credentials if stored, nil otherwise
func credentials(for instance: Instance) -> YatteeServerCredential? { func credentials(for instance: Instance) -> BasicAuthCredential? {
let account = instance.id.uuidString let account = instance.id.uuidString
let query: [String: Any] = [ let query: [String: Any] = [
@@ -110,9 +118,9 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
guard status == errSecSuccess, guard status == errSecSuccess,
let data = result as? Data, let data = result as? Data,
let credential = try? JSONDecoder().decode(YatteeServerCredential.self, from: data) else { let credential = try? JSONDecoder().decode(BasicAuthCredential.self, from: data) else {
LoggingService.shared.debug( LoggingService.shared.debug(
"No credentials found for Yattee Server instance", "No basic auth credentials found for instance",
category: .keychain, category: .keychain,
details: "instanceID=\(instance.id), status=\(status)" details: "instanceID=\(instance.id), status=\(status)"
) )
@@ -124,7 +132,7 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
/// Deletes credentials for an instance. /// Deletes credentials for an instance.
/// Deletes both synced and non-synced items. /// Deletes both synced and non-synced items.
/// - Parameter instance: The Yattee Server instance /// - Parameter instance: The instance
func deleteCredentials(for instance: Instance) { func deleteCredentials(for instance: Instance) {
let account = instance.id.uuidString let account = instance.id.uuidString
@@ -139,13 +147,13 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
if status == errSecSuccess || status == errSecItemNotFound { if status == errSecSuccess || status == errSecItemNotFound {
LoggingService.shared.info( LoggingService.shared.info(
"Deleted credentials for Yattee Server instance", "Deleted basic auth credentials for instance",
category: .keychain, category: .keychain,
details: "instanceID=\(instance.id)" details: "instanceID=\(instance.id)"
) )
} else { } else {
LoggingService.shared.error( LoggingService.shared.error(
"Failed to delete credentials for Yattee Server instance", "Failed to delete basic auth credentials for instance",
category: .keychain, category: .keychain,
details: "instanceID=\(instance.id), status=\(status)" details: "instanceID=\(instance.id), status=\(status)"
) )
@@ -156,7 +164,7 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
} }
/// Checks if an instance has stored credentials. /// Checks if an instance has stored credentials.
/// - Parameter instance: The Yattee Server instance /// - Parameter instance: The instance
/// - Returns: true if credentials exist, false otherwise /// - Returns: true if credentials exist, false otherwise
func hasCredentials(for instance: Instance) -> Bool { func hasCredentials(for instance: Instance) -> Bool {
// Check tracked set first for performance // Check tracked set first for performance
@@ -184,15 +192,15 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
// MARK: - InstanceCredentialsManager Protocol // MARK: - InstanceCredentialsManager Protocol
/// Stores a credential for an instance (protocol conformance). /// Stores a credential for an instance (protocol conformance).
/// The credential is expected to be a JSON-encoded YatteeServerCredential. /// The credential is expected to be a JSON-encoded BasicAuthCredential.
/// - Parameters: /// - Parameters:
/// - credential: JSON string containing {"username": "...", "password": "..."} /// - credential: JSON string containing {"username": "...", "password": "..."}
/// - instance: The instance to associate the credential with /// - instance: The instance to associate the credential with
func setCredential(_ credential: String, for instance: Instance) { func setCredential(_ credential: String, for instance: Instance) {
guard let data = credential.data(using: .utf8), guard let data = credential.data(using: .utf8),
let creds = try? JSONDecoder().decode(YatteeServerCredential.self, from: data) else { let creds = try? JSONDecoder().decode(BasicAuthCredential.self, from: data) else {
LoggingService.shared.error( LoggingService.shared.error(
"Failed to decode credential string for Yattee Server", "Failed to decode basic auth credential string",
category: .keychain category: .keychain
) )
return return
@@ -229,7 +237,7 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
// MARK: - Basic Auth Header Generation // MARK: - Basic Auth Header Generation
/// Generates the HTTP Basic Auth header value for an instance. /// Generates the HTTP Basic Auth header value for an instance.
/// - Parameter instance: The Yattee Server instance /// - Parameter instance: The instance
/// - Returns: The Authorization header value (e.g., "Basic dXNlcjpwYXNz") or nil if no credentials /// - Returns: The Authorization header value (e.g., "Basic dXNlcjpwYXNz") or nil if no credentials
func basicAuthHeader(for instance: Instance) -> String? { func basicAuthHeader(for instance: Instance) -> String? {
guard let creds = credentials(for: instance) else { return nil } guard let creds = credentials(for: instance) else { return nil }
@@ -237,4 +245,81 @@ final class YatteeServerCredentialsManager: InstanceCredentialsManager {
guard let data = credentials.data(using: .utf8) else { return nil } guard let data = credentials.data(using: .utf8) else { return nil }
return "Basic \(data.base64EncodedString())" return "Basic \(data.base64EncodedString())"
} }
// MARK: - Legacy Migration
/// One-time migration of credentials from the legacy Yattee-Server-only Keychain service
/// (`com.yattee.yatteeserver`) to the generic basic-auth service (`com.yattee.basicauth`).
/// Items are copied (preserving sync attribute) and then deleted from the legacy service.
private func migrateLegacyKeychainItems() {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: legacyKeychainServiceName,
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
kSecReturnAttributes as String: true,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let items = result as? [[String: Any]], !items.isEmpty else {
return
}
var migrated = 0
for item in items {
guard let account = item[kSecAttrAccount as String] as? String,
let data = item[kSecValueData as String] as? Data else {
continue
}
let synchronizable = (item[kSecAttrSynchronizable as String] as? Bool) ?? false
// Skip add if a new-style item already exists for this account.
let existsQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainServiceName,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
kSecMatchLimit as String: kSecMatchLimitOne
]
if SecItemCopyMatching(existsQuery as CFDictionary, nil) != errSecSuccess {
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainServiceName,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String: synchronizable,
kSecValueData as String: data
]
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
if addStatus != errSecSuccess {
LoggingService.shared.error(
"Failed to migrate legacy basic auth credential",
category: .keychain,
details: "account=\(account), status=\(addStatus)"
)
continue
}
migrated += 1
}
// Delete the legacy item for this account
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: legacyKeychainServiceName,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny
]
SecItemDelete(deleteQuery as CFDictionary)
}
if migrated > 0 {
LoggingService.shared.info(
"Migrated legacy basic auth credentials to generic Keychain service",
category: .keychain,
details: "count=\(migrated)"
)
}
}
} }

View File

@@ -492,7 +492,7 @@ final class SubscriptionFeedCache {
do { do {
let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient()) let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient())
let authHeader = appEnvironment.yatteeServerCredentialsManager.basicAuthHeader(for: instance) let authHeader = appEnvironment.basicAuthCredentialsManager.basicAuthHeader(for: instance)
await yatteeServerAPI.setAuthHeader(authHeader) await yatteeServerAPI.setAuthHeader(authHeader)
LoggingService.shared.debug("refreshFromStatelessServer: Calling postFeed for \(channelRequests.count) channels", category: .general) LoggingService.shared.debug("refreshFromStatelessServer: Calling postFeed for \(channelRequests.count) channels", category: .general)
let response = try await yatteeServerAPI.postFeed( let response = try await yatteeServerAPI.postFeed(
@@ -556,7 +556,7 @@ final class SubscriptionFeedCache {
StatelessChannelStatusRequest(channelId: $0.channelId, site: $0.site) StatelessChannelStatusRequest(channelId: $0.channelId, site: $0.site)
} }
let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient()) let yatteeServerAPI = YatteeServerAPI(httpClient: HTTPClient())
let authHeader = appEnvironment.yatteeServerCredentialsManager.basicAuthHeader(for: instance) let authHeader = appEnvironment.basicAuthCredentialsManager.basicAuthHeader(for: instance)
await yatteeServerAPI.setAuthHeader(authHeader) await yatteeServerAPI.setAuthHeader(authHeader)
let maxRetries = 5 let maxRetries = 5

View File

@@ -76,13 +76,13 @@ struct InstanceBrowseView: View {
/// Auth header for Yattee Server instances (when browsing a Yattee Server directly) /// Auth header for Yattee Server instances (when browsing a Yattee Server directly)
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard instance.type == .yatteeServer else { return nil } guard instance.type == .yatteeServer else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: instance) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: instance)
} }
/// Auth header for avatar loading (uses Yattee Server for YouTube channel avatars) /// Auth header for avatar loading (uses Yattee Server for YouTube channel avatars)
private var avatarAuthHeader: String? { private var avatarAuthHeader: String? {
guard let server = yatteeServer else { return nil } guard let server = yatteeServer else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
enum BrowseTab: String, CaseIterable, Identifiable { enum BrowseTab: String, CaseIterable, Identifiable {

View File

@@ -54,7 +54,7 @@ struct UnifiedTabView: View {
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil } guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
var body: some View { var body: some View {
@@ -329,7 +329,7 @@ struct UnifiedTabView: View {
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil } guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
var body: some View { var body: some View {
@@ -554,7 +554,7 @@ struct UnifiedTabView: View {
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil } guard let server = appEnvironment?.instancesManager.enabledYatteeServerInstances.first else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
var body: some View { var body: some View {

View File

@@ -529,7 +529,7 @@ struct AddRemoteServerView: View {
allowInvalidCertificates: allowInvalidCertificates allowInvalidCertificates: allowInvalidCertificates
) )
appEnvironment.yatteeServerCredentialsManager.setCredentials( appEnvironment.basicAuthCredentialsManager.setCredentials(
username: yatteeServerUsername, username: yatteeServerUsername,
password: yatteeServerPassword, password: yatteeServerPassword,
for: instance for: instance

View File

@@ -346,7 +346,7 @@ private struct EditRemoteServerContent: View {
// Load existing Yattee Server credentials // Load existing Yattee Server credentials
if instance.type == .yatteeServer, if instance.type == .yatteeServer,
let credentials = appEnvironment?.yatteeServerCredentialsManager.credentials(for: instance) { let credentials = appEnvironment?.basicAuthCredentialsManager.credentials(for: instance) {
yatteeServerUsername = credentials.username yatteeServerUsername = credentials.username
yatteeServerPassword = credentials.password yatteeServerPassword = credentials.password
} }
@@ -438,7 +438,7 @@ private struct EditRemoteServerContent: View {
// Save Yattee Server credentials if provided // Save Yattee Server credentials if provided
if instance.type == .yatteeServer && !yatteeServerUsername.isEmpty && !yatteeServerPassword.isEmpty { if instance.type == .yatteeServer && !yatteeServerUsername.isEmpty && !yatteeServerPassword.isEmpty {
appEnvironment?.yatteeServerCredentialsManager.setCredentials( appEnvironment?.basicAuthCredentialsManager.setCredentials(
username: yatteeServerUsername, username: yatteeServerUsername,
password: yatteeServerPassword, password: yatteeServerPassword,
for: instance for: instance

View File

@@ -290,7 +290,7 @@ private struct ChannelNotificationToggle: View {
private var authHeader: String? { private var authHeader: String? {
guard let server = yatteeServer else { return nil } guard let server = yatteeServer else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
private var toggleBinding: Binding<Bool> { private var toggleBinding: Binding<Bool> {

View File

@@ -45,7 +45,7 @@ struct ManageChannelsView: View {
private var yatteeServerURL: URL? { yatteeServer?.url } private var yatteeServerURL: URL? { yatteeServer?.url }
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard let server = yatteeServer else { return nil } guard let server = yatteeServer else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
/// Channels filtered by search query and sorted by selected order. /// Channels filtered by search query and sorted by selected order.
@@ -439,7 +439,7 @@ struct ManageChannelsView: View {
do { do {
let api = YatteeServerAPI(httpClient: HTTPClient()) let api = YatteeServerAPI(httpClient: HTTPClient())
let authHeader = appEnvironment.yatteeServerCredentialsManager.basicAuthHeader(for: yatteeServer) let authHeader = appEnvironment.basicAuthCredentialsManager.basicAuthHeader(for: yatteeServer)
await api.setAuthHeader(authHeader) await api.setAuthHeader(authHeader)
let response = try await api.channelsMetadata(channelIDs: channelIDs, instance: yatteeServer) let response = try await api.channelsMetadata(channelIDs: channelIDs, instance: yatteeServer)

View File

@@ -52,7 +52,7 @@ struct SubscriptionsView: View {
private var yatteeServerURL: URL? { yatteeServer?.url } private var yatteeServerURL: URL? { yatteeServer?.url }
private var yatteeServerAuthHeader: String? { private var yatteeServerAuthHeader: String? {
guard let server = yatteeServer else { return nil } guard let server = yatteeServer else { return nil }
return appEnvironment?.yatteeServerCredentialsManager.basicAuthHeader(for: server) return appEnvironment?.basicAuthCredentialsManager.basicAuthHeader(for: server)
} }
/// Generates a unique ID based on instances configuration. /// Generates a unique ID based on instances configuration.