Skip to content

Commit

Permalink
patch (AccountManager): handle auth failures during runtime
Browse files Browse the repository at this point in the history
Logs out the current user when the ws closes with an error. Switches to another account if the app starts and the selected user’s token is invalid
  • Loading branch information
cryptoAlgorithm committed Oct 15, 2022
1 parent a6639a0 commit 213a0c5
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 21 deletions.
26 changes: 24 additions & 2 deletions Swiftcord/SwiftcordApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import DiscordKit
import DiscordKitCore
import SwiftUI
import OSLog

// There's probably a better place to put global constants
let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String
Expand All @@ -16,8 +17,8 @@ let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String
struct SwiftcordApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

static internal let tokenKeychainKey = "authTokens"
static internal let legacyTokenKeychainKey = "authToken"
internal static let tokenKeychainKey = "authTokens"
internal static let legacyTokenKeychainKey = "authToken"

// let persistenceController = PersistenceController.shared
#if !APP_STORE
Expand All @@ -30,6 +31,8 @@ struct SwiftcordApp: App {

@AppStorage("theme") private var selectedTheme = "system"

private static let log = Logger(category: "MainApp")

var body: some Scene {
WindowGroup {
if state.attemptLogin {
Expand Down Expand Up @@ -59,6 +62,25 @@ struct SwiftcordApp: App {
}
gateway.connect(token: token)
restAPI.setToken(token: token)
_ = gateway.onAuthFailure.addHandler {
Self.log.warning("Auth failed")
guard acctManager.getActiveID() != nil else {
Self.log.error("Current ID not found! Showing login instead.")
state.attemptLogin = true
state.loadingState = .initial
return
}
acctManager.invalidate()
// Switch to other account if possible
if let token = acctManager.getActiveToken() {
Self.log.debug("Attempting connection with other account")
gateway.connect(token: token)
restAPI.setToken(token: token)
} else {
state.attemptLogin = true
state.loadingState = .initial
}
}
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions Swiftcord/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,6 @@ struct ContentView: View {
.onAppear {
if state.loadingState == .messageLoad { loadLastSelectedGuild() }

_ = gateway.onAuthFailure.addHandler {
state.attemptLogin = true
state.loadingState = .initial
log.debug("Attempting login")
}
_ = gateway.onEvent.addHandler { (evt, data) in
switch evt {
case .ready:
Expand Down
2 changes: 1 addition & 1 deletion Swiftcord/Views/User/AccountSwitcher/AccountMeta.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation
import DiscordKitCommon

struct AccountMeta: Codable, Equatable {
struct AccountMeta: Codable, Equatable, Identifiable {
let id: Snowflake
let discrim: String
let name: String
Expand Down
43 changes: 30 additions & 13 deletions Swiftcord/Views/User/AccountSwitcher/AccountSwitcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class AccountSwitcher: NSObject, ObservableObject {
/// A cache for storing decoded tokens from UserDefaults
private var tokens: [String: String] = [:]

// MARK: - Account metadata methods
func loadAccounts() {
guard let dec = try? JSONDecoder().decode(
[AccountMeta].self,
Expand All @@ -39,7 +40,11 @@ public class AccountSwitcher: NSObject, ObservableObject {
func writeAccounts() {
UserDefaults.standard.setValue(try? JSONEncoder().encode(accounts), forKey: AccountSwitcher.META_KEY)
}
func removeAccount(for id: Snowflake) {
accounts.removeAll(identifiedBy: id)
}

// MARK: - Secure token storage methods
func saveToken(for id: Snowflake, token: String) {
tokens[id] = token
// Keychain.save(key: "\(SwiftcordApp.tokenKeychainKey).\(id)", data: token)
Expand Down Expand Up @@ -84,34 +89,45 @@ public class AccountSwitcher: NSObject, ObservableObject {
}
func logOut(id: Snowflake) async {
guard let token = getToken(for: id) else { return }
await DiscordREST(token: token).logOut()
Keychain.remove(key: "\(SwiftcordApp.tokenKeychainKey).\(id)")
removeToken(for: id)

DispatchQueue.main.async { [weak self] in
withAnimation {
self?.accounts.removeAll { $0.id == id }
self?.removeAccount(for: id)
self?.writeAccounts()

// Actions to take if the account being logged out is the current one
if UserDefaults.standard.string(forKey: AccountSwitcher.ACTIVE_KEY) == id {
AccountSwitcher.clearAccountSpecificPrefKeys()
if let firstID = self?.accounts.first?.id {
self?.setActiveAccount(id: firstID)
} else {
UserDefaults.standard.removeObject(forKey: AccountSwitcher.ACTIVE_KEY)
}
self?.setActiveAccount(id: self?.accounts.first?.id)
}
}
}

await DiscordREST(token: token).logOut()
}
/// Mark the current user as invalid - i.e. remove it from the token store and acc
///
/// The next account (if present) will automatically become "active" after invalidating the current one.
/// > Note: The user will not be signed out from the Discord API
func invalidate() {
guard let id = getActiveID() else { return }
removeToken(for: id)
removeAccount(for: id)
print("has accounts? \(!accounts.isEmpty)")
setActiveAccount(id: accounts.first?.id)
}

func setPendingToken(token: String) {
pendingToken = token
}
func setActiveAccount(id: Snowflake) {
// ID is always assumed to be correct
UserDefaults.standard.set(id, forKey: AccountSwitcher.ACTIVE_KEY)
func setActiveAccount(id: Snowflake?) {
AccountSwitcher.clearAccountSpecificPrefKeys() // Clear account specific UserDefault keys
if let id = id {
// ID is always assumed to be correct
UserDefaults.standard.set(id, forKey: AccountSwitcher.ACTIVE_KEY)
} else {
UserDefaults.standard.removeObject(forKey: AccountSwitcher.ACTIVE_KEY)
}
}

// Multiple sanity checks ensure account meta is valid, if not, repair is attempted
Expand All @@ -121,7 +137,7 @@ public class AccountSwitcher: NSObject, ObservableObject {
AccountSwitcher.log.info("Found token in old key! Logging in with this token...")
return oldToken
}
let storedActiveID = UserDefaults.standard.string(forKey: AccountSwitcher.ACTIVE_KEY)
let storedActiveID = getActiveID()
guard let activeID = storedActiveID != nil && accounts.contains(where: { $0.id == storedActiveID })
? storedActiveID
: accounts.first?.id else { return nil } // Account not signed in
Expand All @@ -133,6 +149,7 @@ public class AccountSwitcher: NSObject, ObservableObject {
}
return token
}
func getActiveID() -> String? { UserDefaults.standard.string(forKey: AccountSwitcher.ACTIVE_KEY) }

func onSignedIn(with user: CurrentUser) {
// Migrate from old keychain key to new keys
Expand Down

0 comments on commit 213a0c5

Please sign in to comment.