mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
Debrid: Migrate auth to protocol
Unify authentication to the new protocol. Also remove logout on invalid requests. This became annoying and didn't update the UI properly. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
243a16e3c4
commit
973fbb4099
8 changed files with 118 additions and 166 deletions
|
|
@ -19,6 +19,14 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
getToken() != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "AllDebrid.UseManualKey") {
|
||||
return getToken()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
|
|
@ -101,11 +109,9 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a manual API key instead of web auth
|
||||
public func setApiKey(_ key: String) -> Bool {
|
||||
public func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "AllDebrid.ApiKey")
|
||||
UserDefaults.standard.set(true, forKey: "AllDebrid.UseManualKey")
|
||||
|
||||
return FerriteKeychain.shared.get("AllDebrid.ApiKey") == key
|
||||
}
|
||||
|
||||
public func getToken() -> String? {
|
||||
|
|
@ -137,7 +143,6 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
if response.statusCode >= 200, response.statusCode <= 299 {
|
||||
return data
|
||||
} else if response.statusCode == 401 {
|
||||
logout()
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to AllDebrid in Settings.")
|
||||
} else {
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,15 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
public let website = "https://premiumize.me"
|
||||
@Published public var authProcessing: Bool = false
|
||||
public var isLoggedIn: Bool {
|
||||
getToken() != nil
|
||||
return getToken() != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "Premiumize.UseManualKey") {
|
||||
return getToken()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
|
|
@ -62,11 +70,9 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a manual API key instead of web auth
|
||||
public func setApiKey(_ key: String) -> Bool {
|
||||
public func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "Premiumize.AccessToken")
|
||||
UserDefaults.standard.set(true, forKey: "Premiumize.UseManualKey")
|
||||
|
||||
return FerriteKeychain.shared.get("Premiumize.AccessToken") == key
|
||||
}
|
||||
|
||||
public func getToken() -> String? {
|
||||
|
|
@ -118,7 +124,6 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
if response.statusCode >= 200, response.statusCode <= 299 {
|
||||
return data
|
||||
} else if response.statusCode == 401 {
|
||||
logout()
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to Premiumize in Settings.")
|
||||
} else {
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||
|
|
|
|||
|
|
@ -15,11 +15,19 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
|
||||
@Published public var authProcessing: Bool = false
|
||||
|
||||
// Directly checked because the request fetch uses async
|
||||
// Check the manual token since getTokens() is async
|
||||
public var isLoggedIn: Bool {
|
||||
FerriteKeychain.shared.get("RealDebrid.AccessToken") != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "RealDebrid.UseManualKey") {
|
||||
return FerriteKeychain.shared.get("RealDebrid.AccessToken")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
|
|
@ -175,14 +183,12 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
|
||||
// Adds a manual API key instead of web auth
|
||||
// Clear out existing refresh tokens and timestamps
|
||||
public func setApiKey(_ key: String) -> Bool {
|
||||
public func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "RealDebrid.AccessToken")
|
||||
FerriteKeychain.shared.delete("RealDebrid.RefreshToken")
|
||||
FerriteKeychain.shared.delete("RealDebrid.AccessTokenStamp")
|
||||
|
||||
UserDefaults.standard.set(true, forKey: "RealDebrid.UseManualKey")
|
||||
|
||||
return FerriteKeychain.shared.get("RealDebrid.AccessToken") == key
|
||||
}
|
||||
|
||||
// Deletes tokens from device and RD's servers
|
||||
|
|
@ -222,7 +228,6 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
if response.statusCode >= 200, response.statusCode <= 299 {
|
||||
return data
|
||||
} else if response.statusCode == 401 {
|
||||
await logout()
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to RealDebrid in Settings.")
|
||||
} else {
|
||||
throw DebridError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ public protocol DebridSource: AnyObservableObject {
|
|||
var authProcessing: Bool { get set }
|
||||
var isLoggedIn: Bool { get }
|
||||
|
||||
// Manual API key
|
||||
var manualToken: String? { get }
|
||||
|
||||
// Common authentication functions
|
||||
func setApiKey(_ key: String) -> Bool
|
||||
func setApiKey(_ key: String)
|
||||
func logout() async
|
||||
|
||||
// Instant availability variables
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ public class DebridManager: ObservableObject {
|
|||
debridSources.contains { $0.isLoggedIn }
|
||||
}
|
||||
|
||||
var enabledDebridCount: Int {
|
||||
debridSources.filter{ $0.isLoggedIn }.count
|
||||
}
|
||||
|
||||
@Published var selectedDebridSource: DebridSource? {
|
||||
didSet {
|
||||
UserDefaults.standard.set(selectedDebridSource?.id ?? "", forKey: "Debrid.PreferredService")
|
||||
|
|
@ -34,18 +38,8 @@ public class DebridManager: ObservableObject {
|
|||
var selectedDebridItem: DebridIA?
|
||||
var selectedDebridFile: DebridIAFile?
|
||||
|
||||
// Service agnostic variables
|
||||
@Published var enabledDebrids: Set<DebridType> = [] {
|
||||
didSet {
|
||||
UserDefaults.standard.set(enabledDebrids.rawValue, forKey: "Debrid.EnabledArray")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var selectedDebridType: DebridType? {
|
||||
didSet {
|
||||
UserDefaults.standard.set(selectedDebridType?.rawValue ?? 0, forKey: "Debrid.PreferredService")
|
||||
}
|
||||
}
|
||||
// TODO: Figure out a way to remove this var
|
||||
var selectedOAuthDebridSource: OAuthDebridSource?
|
||||
|
||||
@Published var filteredIAStatus: Set<IAStatus> = []
|
||||
|
||||
|
|
@ -53,22 +47,6 @@ public class DebridManager: ObservableObject {
|
|||
var downloadUrl: String = ""
|
||||
var authUrl: URL?
|
||||
|
||||
// Is the current debrid type processing an auth request
|
||||
func authProcessing(_ passedDebridType: DebridType?) -> Bool {
|
||||
guard let debridType = passedDebridType ?? selectedDebridType else {
|
||||
return false
|
||||
}
|
||||
|
||||
switch debridType {
|
||||
case .realDebrid:
|
||||
return realDebridAuthProcessing
|
||||
case .allDebrid:
|
||||
return allDebridAuthProcessing
|
||||
case .premiumize:
|
||||
return premiumizeAuthProcessing
|
||||
}
|
||||
}
|
||||
|
||||
// RealDebrid auth variables
|
||||
var realDebridAuthProcessing: Bool = false
|
||||
|
||||
|
|
@ -89,7 +67,6 @@ public class DebridManager: ObservableObject {
|
|||
if let preferredServiceInt = Int(rawPreferredService) {
|
||||
debridServiceId = migratePreferredService(preferredServiceInt)
|
||||
} else {
|
||||
print(rawPreferredService)
|
||||
debridServiceId = rawPreferredService
|
||||
}
|
||||
|
||||
|
|
@ -207,73 +184,62 @@ public class DebridManager: ObservableObject {
|
|||
// MARK: - Authentication UI linked functions
|
||||
|
||||
// Common function to delegate what debrid service to authenticate with
|
||||
public func authenticateDebrid(debridType: DebridType, apiKey: String?) async {
|
||||
switch debridType {
|
||||
case .realDebrid:
|
||||
let success = apiKey == nil ? await authenticateRd() : realDebrid.setApiKey(apiKey!)
|
||||
completeDebridAuth(debridType, success: success)
|
||||
case .allDebrid:
|
||||
// Async can't work with nil mapping method
|
||||
let success = apiKey == nil ? await authenticateAd() : allDebrid.setApiKey(apiKey!)
|
||||
completeDebridAuth(debridType, success: success)
|
||||
case .premiumize:
|
||||
if let apiKey {
|
||||
let success = premiumize.setApiKey(apiKey)
|
||||
completeDebridAuth(debridType, success: success)
|
||||
} else {
|
||||
await authenticatePm()
|
||||
public func authenticateDebrid(_ debridSource: some DebridSource, apiKey: String?) async {
|
||||
defer {
|
||||
// Don't cancel processing if using OAuth
|
||||
if !(debridSource is OAuthDebridSource) {
|
||||
debridSource.authProcessing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Callback to finish debrid auth since functions can be split
|
||||
func completeDebridAuth(_ debridType: DebridType, success: Bool) {
|
||||
if success {
|
||||
enabledDebrids.insert(debridType)
|
||||
if enabledDebrids.count == 1 {
|
||||
selectedDebridType = enabledDebrids.first
|
||||
if enabledDebridCount == 1 {
|
||||
selectedDebridSource = debridSource
|
||||
}
|
||||
}
|
||||
|
||||
switch debridType {
|
||||
case .realDebrid:
|
||||
realDebridAuthProcessing = false
|
||||
case .allDebrid:
|
||||
allDebridAuthProcessing = false
|
||||
case .premiumize:
|
||||
premiumizeAuthProcessing = false
|
||||
// Set an API key if manually provided
|
||||
if let apiKey {
|
||||
debridSource.setApiKey(apiKey)
|
||||
return
|
||||
}
|
||||
|
||||
// Processing has started
|
||||
debridSource.authProcessing = true
|
||||
|
||||
if let pollingSource = debridSource as? PollingDebridSource {
|
||||
do {
|
||||
let authUrl = try await pollingSource.getAuthUrl()
|
||||
|
||||
if validateAuthUrl(authUrl) {
|
||||
try await pollingSource.authTask?.value
|
||||
} else {
|
||||
throw DebridError.AuthQuery(description: "The authentication URL was invalid")
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "\(debridSource.id) authentication error")
|
||||
|
||||
pollingSource.authTask?.cancel()
|
||||
}
|
||||
} else if let oauthSource = debridSource as? OAuthDebridSource {
|
||||
do {
|
||||
let tempAuthUrl = try oauthSource.getAuthUrl()
|
||||
selectedOAuthDebridSource = oauthSource
|
||||
|
||||
validateAuthUrl(tempAuthUrl, useAuthSession: true)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "\(debridSource.id) authentication error")
|
||||
}
|
||||
} else {
|
||||
logManager?.error(
|
||||
"DebridManager: Auth: Could not figure out the authentication type for \(debridSource.id). Is this configured properly?"
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get a truncated manual API key if it's being used
|
||||
func getManualAuthKey(_ passedDebridType: DebridType?) async -> String? {
|
||||
guard let debridType = passedDebridType ?? selectedDebridType else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let debridToken: String?
|
||||
switch debridType {
|
||||
case .realDebrid:
|
||||
if UserDefaults.standard.bool(forKey: "RealDebrid.UseManualKey") {
|
||||
debridToken = FerriteKeychain.shared.get("RealDebrid.AccessToken")
|
||||
} else {
|
||||
debridToken = nil
|
||||
}
|
||||
case .allDebrid:
|
||||
if UserDefaults.standard.bool(forKey: "AllDebrid.UseManualKey") {
|
||||
debridToken = FerriteKeychain.shared.get("AllDebrid.ApiKey")
|
||||
} else {
|
||||
debridToken = nil
|
||||
}
|
||||
case .premiumize:
|
||||
if UserDefaults.standard.bool(forKey: "Premiumize.UseManualKey") {
|
||||
debridToken = FerriteKeychain.shared.get("Premiumize.AccessToken")
|
||||
} else {
|
||||
debridToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let debridToken {
|
||||
func getManualAuthKey(_ debridSource: some DebridSource) async -> String? {
|
||||
if let debridToken = debridSource.manualToken {
|
||||
let splitString = debridToken.suffix(4)
|
||||
|
||||
if debridToken.count > 4 {
|
||||
|
|
@ -303,74 +269,42 @@ public class DebridManager: ObservableObject {
|
|||
return true
|
||||
}
|
||||
|
||||
private func authenticateRd() async -> Bool {
|
||||
do {
|
||||
realDebridAuthProcessing = true
|
||||
let authUrl = try await realDebrid.getAuthUrl()
|
||||
|
||||
if validateAuthUrl(authUrl) {
|
||||
try await realDebrid.authTask?.value
|
||||
return true
|
||||
} else {
|
||||
throw DebridError.AuthQuery(description: "The verification URL was invalid")
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "RealDebrid authentication error")
|
||||
|
||||
realDebrid.authTask?.cancel()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticateAd() async -> Bool {
|
||||
do {
|
||||
allDebridAuthProcessing = true
|
||||
let authUrl = try await allDebrid.getAuthUrl()
|
||||
|
||||
if validateAuthUrl(authUrl) {
|
||||
try await allDebrid.authTask?.value
|
||||
return true
|
||||
} else {
|
||||
throw DebridError.AuthQuery(description: "The PIN URL was invalid")
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "AllDebrid authentication error")
|
||||
|
||||
allDebrid.authTask?.cancel()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticatePm() async {
|
||||
do {
|
||||
premiumizeAuthProcessing = true
|
||||
let tempAuthUrl = try premiumize.getAuthUrl()
|
||||
|
||||
validateAuthUrl(tempAuthUrl, useAuthSession: true)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "Premiumize authentication error")
|
||||
|
||||
completeDebridAuth(.premiumize, success: false)
|
||||
}
|
||||
}
|
||||
|
||||
// Currently handles Premiumize callback
|
||||
public func handleCallback(url: URL?, error: Error?) async {
|
||||
public func handleAuthCallback(url: URL?, error: Error?) async {
|
||||
defer {
|
||||
if enabledDebridCount == 1 {
|
||||
selectedDebridSource = selectedOAuthDebridSource
|
||||
}
|
||||
|
||||
selectedOAuthDebridSource?.authProcessing = false
|
||||
}
|
||||
|
||||
do {
|
||||
guard let oauthDebridSource = selectedOAuthDebridSource else {
|
||||
throw DebridError.AuthQuery(description: "OAuth source couldn't be found for callback. Aborting.")
|
||||
}
|
||||
|
||||
if let error {
|
||||
throw DebridError.AuthQuery(description: "OAuth callback Error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if let callbackUrl = url {
|
||||
try premiumize.handleAuthCallback(url: callbackUrl)
|
||||
completeDebridAuth(.premiumize, success: true)
|
||||
try oauthDebridSource.handleAuthCallback(url: callbackUrl)
|
||||
} else {
|
||||
throw DebridError.AuthQuery(description: "The callback URL was invalid")
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "Premiumize authentication error (callback)")
|
||||
}
|
||||
}
|
||||
|
||||
completeDebridAuth(.premiumize, success: false)
|
||||
// MARK: - Logout UI functions
|
||||
|
||||
public func logout(_ debridSource: some DebridSource) async {
|
||||
await debridSource.logout()
|
||||
|
||||
if selectedDebridSource?.id == debridSource.id {
|
||||
selectedDebridSource = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ struct DebridCloudView: View {
|
|||
.refreshable {
|
||||
await debridManager.fetchDebridCloud(bypassTTL: true)
|
||||
}
|
||||
.onChange(of: debridManager.selectedDebridType) { newType in
|
||||
.onChange(of: debridManager.selectedDebridSource?.id) { newType in
|
||||
if newType != nil {
|
||||
Task {
|
||||
await debridManager.fetchDebridCloud()
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ struct SettingsDebridInfoView: View {
|
|||
Button {
|
||||
Task {
|
||||
if debridSource.isLoggedIn {
|
||||
await debridSource.logout()
|
||||
await debridManager.logout(debridSource)
|
||||
} else if !debridSource.authProcessing {
|
||||
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: nil)
|
||||
await debridManager.authenticateDebrid(debridSource, apiKey: nil)
|
||||
}
|
||||
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridSource) ?? ""
|
||||
}
|
||||
} label: {
|
||||
Text(
|
||||
|
|
@ -57,8 +57,8 @@ struct SettingsDebridInfoView: View {
|
|||
onCommit: {
|
||||
Task {
|
||||
if !apiKeyTempText.isEmpty {
|
||||
//await debridManager.authenticateDebrid(debridType: debridType, apiKey: apiKeyTempText)
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
await debridManager.authenticateDebrid(debridSource, apiKey: apiKeyTempText)
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridSource) ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ struct SettingsDebridInfoView: View {
|
|||
}
|
||||
.onAppear {
|
||||
Task {
|
||||
//apiKeyTempText = await debridManager.getManualAuthKey(debridType) ?? ""
|
||||
apiKeyTempText = await debridManager.getManualAuthKey(debridSource) ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ struct SettingsView: View {
|
|||
callbackURLScheme: "ferrite"
|
||||
) { callbackURL, error in
|
||||
Task {
|
||||
await debridManager.handleCallback(url: callbackURL, error: error)
|
||||
await debridManager.handleAuthCallback(url: callbackURL, error: error)
|
||||
}
|
||||
}
|
||||
.prefersEphemeralWebBrowserSession(useEphemeralAuth)
|
||||
|
|
|
|||
Loading…
Reference in a new issue