Fixes #239 - Use new restoration token format, pass stored homeserver url to the client builder when restoring, dropping the need for a network request

- bumped the rust sdk to v1.0.17-alpha
- move all UserSession related files to Services/UserSession
This commit is contained in:
Stefan Ceriu
2022-11-04 13:24:53 +02:00
parent 2e86313965
commit 37cfb8f50e
18 changed files with 271 additions and 179 deletions
@@ -95,7 +95,7 @@ class ClientProxy: ClientProxyProtocol {
.build()
self.slidingSync = try slidingSyncBuilder
.addView(view: slidingSyncView)
.addView(v: slidingSyncView)
.withCommonExtensions()
.build()
@@ -136,9 +136,9 @@ class ClientProxy: ClientProxyProtocol {
client.homeserver()
}
var restoreToken: String? {
var restorationToken: RestorationToken? {
do {
return try client.restoreToken()
return try RestorationToken(session: client.session())
} catch {
MXLog.error("Failed retrieving restore token with error: \(error)")
return nil
@@ -44,7 +44,7 @@ protocol ClientProxyProtocol {
var homeserver: String { get }
var restoreToken: String? { get }
var restorationToken: RestorationToken? { get }
var roomSummaryProvider: RoomSummaryProviderProtocol { get }
@@ -24,7 +24,7 @@ struct MockClientProxy: ClientProxyProtocol {
let isSoftLogout = false
let deviceId: String? = nil
let homeserver = ""
let restoreToken: String? = nil
let restorationToken: RestorationToken? = nil
var roomSummaryProvider: RoomSummaryProviderProtocol = MockRoomSummaryProvider()
@@ -0,0 +1,85 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import KeychainAccess
class KeychainController: KeychainControllerProtocol {
private let keychain: Keychain
init(identifier: String) {
keychain = Keychain(service: identifier)
}
func setRestorationToken(_ restorationToken: RestorationToken, forUsername username: String) {
do {
let tokenData = try JSONEncoder().encode(restorationToken)
try keychain.set(tokenData, key: username)
} catch {
MXLog.error("Failed storing user restore token with error: \(error)")
}
}
func restorationTokenForUsername(_ username: String) -> RestorationToken? {
do {
guard let tokenData = try keychain.getData(username) else {
return nil
}
// Handle the previous restoration token format as we don't want users to have to log in again
// It will automatically be updated to the new version after login
if let legacyRestorationToken = try? JSONDecoder().decode(LegacyRestorationToken.self, from: tokenData) {
return .init(session: .init(accessToken: legacyRestorationToken.session.accessToken,
refreshToken: nil,
userId: legacyRestorationToken.session.userId,
deviceId: legacyRestorationToken.session.deviceId,
homeserverUrl: legacyRestorationToken.homeURL,
isSoftLogout: legacyRestorationToken.isSoftLogout ?? false))
}
return try JSONDecoder().decode(RestorationToken.self, from: tokenData)
} catch {
MXLog.error("Failed retrieving user restore token")
return nil
}
}
func restorationTokens() -> [KeychainCredentials] {
keychain.allKeys().compactMap { username in
guard let restorationToken = restorationTokenForUsername(username) else {
return nil
}
return KeychainCredentials(userID: username, restorationToken: restorationToken)
}
}
func removeRestorationTokenForUsername(_ username: String) {
do {
try keychain.remove(username)
} catch {
MXLog.error("Failed removing restore token with error: \(error)")
}
}
func removeAllRestorationTokens() {
do {
try keychain.removeAll()
} catch {
MXLog.error("Failed removing all tokens")
}
}
}
@@ -18,13 +18,13 @@ import Foundation
struct KeychainCredentials {
let userID: String
let restoreToken: String
let restorationToken: RestorationToken
}
protocol KeychainControllerProtocol {
func setRestoreToken(_ accessToken: String, forUsername username: String)
func restoreTokenForUsername(_ username: String) -> String?
func restoreTokens() -> [KeychainCredentials]
func removeRestoreTokenForUsername(_ username: String)
func removeAllRestoreTokens()
func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String)
func restorationTokenForUsername(_ username: String) -> RestorationToken?
func restorationTokens() -> [KeychainCredentials]
func removeRestorationTokenForUsername(_ username: String)
func removeAllRestorationTokens()
}
@@ -0,0 +1,76 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import MatrixRustSDK
struct RestorationToken: Codable, Equatable {
let session: MatrixRustSDK.Session
}
extension MatrixRustSDK.Session: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self = .init(accessToken: try container.decode(String.self, forKey: .accessToken),
refreshToken: try container.decodeIfPresent(String.self, forKey: .refreshToken),
userId: try container.decode(String.self, forKey: .userId),
deviceId: try container.decode(String.self, forKey: .deviceId),
homeserverUrl: try container.decode(String.self, forKey: .homeserverUrl),
isSoftLogout: try container.decode(Bool.self, forKey: .isSoftLogout))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(accessToken, forKey: .accessToken)
try container.encode(refreshToken, forKey: .refreshToken)
try container.encode(userId, forKey: .userId)
try container.encode(deviceId, forKey: .deviceId)
try container.encode(homeserverUrl, forKey: .homeserverUrl)
try container.encode(isSoftLogout, forKey: .isSoftLogout)
}
enum CodingKeys: String, CodingKey {
case accessToken, refreshToken, userId, deviceId, homeserverUrl, isSoftLogout
}
}
#warning("Remove this in a couple of releases - sceriu 03.11.2022")
struct LegacyRestorationToken: Decodable {
let isGuest: Bool?
let isSoftLogout: Bool?
let homeURL: String
let session: Session
enum CodingKeys: String, CodingKey {
case isGuest = "is_guest"
case isSoftLogout = "is_soft_logout"
case homeURL = "homeurl"
case session
}
struct Session: Decodable {
let accessToken: String
let userId: String
let deviceId: String
// swiftlint:disable:next nesting
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case userId = "user_id"
case deviceId = "device_id"
}
}
}
@@ -0,0 +1,320 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
enum UserSessionFlowCoordinatorAction {
case signOut
}
class UserSessionFlowCoordinator: Coordinator {
private let stateMachine: UserSessionFlowCoordinatorStateMachine
private let userSession: UserSessionProtocol
private let navigationRouter: NavigationRouterType
private let bugReportService: BugReportServiceProtocol
var childCoordinators: [Coordinator] = []
var callback: ((UserSessionFlowCoordinatorAction) -> Void)?
init(userSession: UserSessionProtocol, navigationRouter: NavigationRouterType, bugReportService: BugReportServiceProtocol) {
stateMachine = UserSessionFlowCoordinatorStateMachine()
self.userSession = userSession
self.navigationRouter = navigationRouter
self.bugReportService = bugReportService
setupStateMachine()
startObservingApplicationState()
}
func start() {
stateMachine.processEvent(.start)
}
func stop() { }
// MARK: - Private
// swiftlint:disable:next cyclomatic_complexity
private func setupStateMachine() {
stateMachine.addTransitionHandler { [weak self] context in
guard let self else { return }
switch (context.fromState, context.event, context.toState) {
case (.initial, .start, .homeScreen):
self.presentHomeScreen()
case(.homeScreen, .showRoomScreen, .roomScreen(let roomId)):
self.presentRoomWithIdentifier(roomId)
case(.roomScreen(let roomId), .dismissedRoomScreen, .homeScreen):
self.tearDownDismissedRoomScreen(roomId)
case (.homeScreen, .showSessionVerificationScreen, .sessionVerificationScreen):
self.presentSessionVerification()
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .homeScreen):
self.tearDownDismissedSessionVerificationScreen()
case (.homeScreen, .showSettingsScreen, .settingsScreen):
self.presentSettingsScreen()
case (.settingsScreen, .dismissedSettingsScreen, .homeScreen):
self.dismissSettingsScreen()
case (.homeScreen, .feedbackScreen, .feedbackScreen):
self.presentFeedbackScreen()
case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen):
self.dismissFeedbackScreen()
case (_, .resignActive, .suspended):
self.pause()
case (_, .becomeActive, _):
self.resume()
default:
fatalError("Unknown transition: \(context)")
}
}
stateMachine.addErrorHandler { context in
fatalError("Failed transition with context: \(context)")
}
}
private func startObservingApplicationState() {
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
private func presentHomeScreen() {
userSession.clientProxy.startSync()
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
attributedStringBuilder: AttributedStringBuilder())
let coordinator = HomeScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
guard let self else { return }
switch action {
case .presentRoomScreen(let roomIdentifier):
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
case .presentSettingsScreen:
self.stateMachine.processEvent(.showSettingsScreen)
case .presentFeedbackScreen:
self.stateMachine.processEvent(.feedbackScreen)
case .presentSessionVerificationScreen:
self.stateMachine.processEvent(.showSessionVerificationScreen)
case .signOut:
self.callback?(.signOut)
}
}
add(childCoordinator: coordinator)
navigationRouter.setRootModule(coordinator)
if bugReportService.crashedLastRun {
showCrashPopup()
}
}
// MARK: Rooms
private func presentRoomWithIdentifier(_ roomIdentifier: String) {
Task { @MainActor in
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomIdentifier) else {
MXLog.error("Invalid room identifier: \(roomIdentifier)")
return
}
let userId = userSession.clientProxy.userIdentifier
let timelineItemFactory = RoomTimelineItemFactory(userID: userId,
mediaProvider: userSession.mediaProvider,
roomProxy: roomProxy,
attributedStringBuilder: AttributedStringBuilder())
let timelineController = RoomTimelineController(userId: userId,
roomId: roomIdentifier,
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider,
roomProxy: roomProxy)
let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController,
mediaProvider: userSession.mediaProvider,
roomName: roomProxy.displayName ?? roomProxy.name,
roomAvatarUrl: roomProxy.avatarURL)
let coordinator = RoomScreenCoordinator(parameters: parameters)
add(childCoordinator: coordinator)
navigationRouter.push(coordinator) { [weak self] in
guard let self else { return }
self.stateMachine.processEvent(.dismissedRoomScreen)
}
}
}
private func tearDownDismissedRoomScreen(_ roomId: String) {
guard let coordinator = childCoordinators.last as? RoomScreenCoordinator else {
fatalError("Invalid coordinator hierarchy: \(childCoordinators)")
}
remove(childCoordinator: coordinator)
}
// MARK: Settings
private func presentSettingsScreen() {
let navController = ElementNavigationController()
let newNavigationRouter = NavigationRouter(navigationController: navController)
let parameters = SettingsCoordinatorParameters(navigationRouter: newNavigationRouter,
userSession: userSession,
bugReportService: bugReportService)
let coordinator = SettingsCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
self.dismissSettingsScreen()
case .logout:
self.dismissSettingsScreen()
self.callback?(.signOut)
}
}
add(childCoordinator: coordinator)
coordinator.start()
navController.viewControllers = [coordinator.toPresentable()]
navigationRouter.present(navController, animated: true)
}
@objc
private func dismissSettingsScreen() {
MXLog.debug("dismissSettingsScreen")
guard let coordinator = childCoordinators.first(where: { $0 is SettingsCoordinator }) else {
return
}
navigationRouter.dismissModule()
remove(childCoordinator: coordinator)
stateMachine.processEvent(.dismissedSettingsScreen)
}
// MARK: Session verification
private func presentSessionVerification() {
guard let sessionVerificationController = userSession.sessionVerificationController else {
fatalError("The sessionVerificationController should aways be valid at this point")
}
let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
let coordinator = SessionVerificationCoordinator(parameters: parameters)
coordinator.callback = { [weak self] in
self?.navigationRouter.dismissModule()
self?.stateMachine.processEvent(.dismissedSessionVerificationScreen)
}
add(childCoordinator: coordinator)
navigationRouter.present(coordinator)
coordinator.start()
}
private func tearDownDismissedSessionVerificationScreen() {
guard let coordinator = childCoordinators.last as? SessionVerificationCoordinator else {
fatalError("Invalid coordinator hierarchy: \(childCoordinators)")
}
remove(childCoordinator: coordinator)
}
// MARK: Bug reporting
private func showCrashPopup() {
let alert = UIAlertController(title: nil,
message: ElementL10n.sendBugReportAppCrashed,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel))
alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in
self?.stateMachine.processEvent(.feedbackScreen)
})
navigationRouter.present(alert, animated: true)
}
private func presentFeedbackScreen(for image: UIImage? = nil) {
let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService,
screenshot: image)
let coordinator = BugReportCoordinator(parameters: parameters)
coordinator.completion = { [weak self] in
self?.stateMachine.processEvent(.dismissedFeedbackScreen)
}
add(childCoordinator: coordinator)
coordinator.start()
let navController = ElementNavigationController(rootViewController: coordinator.toPresentable())
navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(handleFeedbackScreenCancellation))
navController.isModalInPresentation = true
navigationRouter.present(navController, animated: true)
}
@objc
private func handleFeedbackScreenCancellation() {
stateMachine.processEvent(.dismissedFeedbackScreen)
}
private func dismissFeedbackScreen() {
guard let coordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else {
return
}
navigationRouter.dismissModule()
remove(childCoordinator: coordinator)
}
// MARK: - Application State
private func pause() {
userSession.clientProxy.stopSync()
}
private func resume() {
userSession.clientProxy.startSync()
}
@objc
private func applicationWillResignActive() {
stateMachine.processEvent(.resignActive)
}
@objc
private func applicationDidBecomeActive() {
stateMachine.processEvent(.becomeActive)
}
}
@@ -0,0 +1,153 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftState
class UserSessionFlowCoordinatorStateMachine {
/// States the AppCoordinator can find itself in
enum State: StateType {
/// The initial state, used before the coordinator starts
case initial
/// Showing the home screen
case homeScreen
/// Showing a particular room's timeline
/// - Parameter roomId: that room's identifier
case roomScreen(roomId: String)
/// Showing the session verification flows
case sessionVerificationScreen
/// Showing the session verification flows
case feedbackScreen
/// Showing the settings screen
case settingsScreen
/// Application has been suspended
case suspended
}
/// Events that can be triggered on the AppCoordinator state machine
enum Event: EventType {
/// Start the user session flows
case start
/// Request presentation for a particular room
/// - Parameter roomId:the room identifier
case showRoomScreen(roomId: String)
/// The room screen has been dismissed
case dismissedRoomScreen
/// Request presentation of the settings screen
case showSettingsScreen
/// The settings screen has been dismissed
case dismissedSettingsScreen
/// Request presentation of the feedback screen
case feedbackScreen
/// The feedback screen has been dismissed
case dismissedFeedbackScreen
/// Request the start of the session verification flow
case showSessionVerificationScreen
/// Session verification has finished
case dismissedSessionVerificationScreen
/// Application goes into inactive state
case resignActive
/// Application goes into active state
case becomeActive
}
private let stateMachine: StateMachine<State, Event>
private var stateBeforeSuspension: State?
init() {
stateMachine = StateMachine(state: .initial)
configure()
}
// swiftlint:disable:next cyclomatic_complexity
private func configure() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen])
stateMachine.addRouteMapping { event, fromState, _ in
switch (event, fromState) {
case (.showRoomScreen(let roomId), .homeScreen):
return .roomScreen(roomId: roomId)
case (.dismissedRoomScreen, .roomScreen):
return .homeScreen
case (.showSettingsScreen, .homeScreen):
return .settingsScreen
case (.dismissedSettingsScreen, .settingsScreen):
return .homeScreen
case (.feedbackScreen, .homeScreen):
return .feedbackScreen
case (.dismissedFeedbackScreen, .feedbackScreen):
return .homeScreen
case (.showSessionVerificationScreen, .homeScreen):
return .sessionVerificationScreen
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen):
return .homeScreen
case (.resignActive, _):
self.stateBeforeSuspension = fromState
return .suspended
case (.becomeActive, _):
// Cannot become active if not previously suspended
// Happens when the app is backgrounded before the session is setup
guard let previousState = self.stateBeforeSuspension else {
return self.stateMachine.state
}
return previousState
default:
return nil
}
}
addTransitionHandler { context in
if let event = context.event {
MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(event)`")
} else {
MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`")
}
}
}
/// Attempt to move the state machine to another state through an event
/// It will either invoke the `transitionHandler` or the `errorHandler` depending on its current state
func processEvent(_ event: Event) {
stateMachine.tryEvent(event)
}
/// Registers a callback for processing state machine transitions
func addTransitionHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
stateMachine.addAnyHandler(.any => .any, handler: handler)
}
/// Registers a callback for processing state machine errors
func addErrorHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
stateMachine.addErrorHandler(handler: handler)
}
}
@@ -23,7 +23,7 @@ class UserSessionStore: UserSessionStoreProtocol {
private let backgroundTaskService: BackgroundTaskServiceProtocol
/// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.restoreTokens().isEmpty }
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
/// The base directory where all session data is stored.
private(set) lazy var baseDirectory: URL = {
@@ -49,7 +49,7 @@ class UserSessionStore: UserSessionStoreProtocol {
}
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError> {
let availableCredentials = keychainController.restoreTokens()
let availableCredentials = keychainController.restorationTokens()
guard let credentials = availableCredentials.first else {
return .failure(.missingCredentials)
@@ -65,7 +65,7 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.error("Failed restoring login with error: \(error)")
// On any restoration failure reset the token and restart
keychainController.removeAllRestoreTokens()
keychainController.removeAllRestorationTokens()
deleteSessionDirectory(for: credentials.userID)
return .failure(error)
@@ -85,19 +85,19 @@ class UserSessionStore: UserSessionStoreProtocol {
}
}
func refreshRestoreToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError> {
guard let accessToken = userSession.clientProxy.restoreToken else {
func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError> {
guard let restorationToken = userSession.clientProxy.restorationToken else {
return .failure(.failedRefreshingRestoreToken)
}
keychainController.setRestoreToken(accessToken, forUsername: userSession.clientProxy.userIdentifier)
keychainController.setRestorationToken(restorationToken, forUsername: userSession.clientProxy.userIdentifier)
return .success(())
}
func logout(userSession: UserSessionProtocol) {
let userID = userSession.clientProxy.userIdentifier
keychainController.removeRestoreTokenForUsername(userID)
keychainController.removeRestorationTokenForUsername(userID)
deleteSessionDirectory(for: userID)
}
@@ -109,12 +109,13 @@ class UserSessionStore: UserSessionStoreProtocol {
let builder = ClientBuilder()
.basePath(path: baseDirectory.path)
.username(username: credentials.userID)
.homeserverUrl(url: credentials.restorationToken.session.homeserverUrl)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent() ?? "unknown")
do {
let client: Client = try await Task.dispatch(on: .global()) {
let client = try builder.build()
try client.restoreLogin(restoreToken: credentials.restoreToken)
try client.restoreSession(session: credentials.restorationToken.session)
return client
}
return await setupProxyForClient(client)
@@ -126,10 +127,10 @@ class UserSessionStore: UserSessionStoreProtocol {
private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
do {
let accessToken = try client.restoreToken()
let session = try client.session()
let userId = try client.userId()
keychainController.setRestoreToken(accessToken, forUsername: userId)
keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userId)
} catch {
MXLog.error("Failed setting up user session with error: \(error)")
return .failure(.failedSettingUpSession)
@@ -38,7 +38,7 @@ protocol UserSessionStoreProtocol {
func userSession(for client: Client) async -> Result<UserSession, UserSessionStoreError>
/// Refresh the restore token of the client for a given session.
func refreshRestoreToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError>
func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError>
/// Logs out of the specified session.
func logout(userSession: UserSessionProtocol)
@@ -1,69 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import KeychainAccess
class KeychainController: KeychainControllerProtocol {
private let keychain: Keychain
init(identifier: String) {
keychain = Keychain(service: identifier)
}
func setRestoreToken(_ restoreToken: String, forUsername username: String) {
do {
try keychain.set(restoreToken, key: username)
} catch {
MXLog.error("Failed storing user restore token with error: \(error)")
}
}
func restoreTokenForUsername(_ username: String) -> String? {
do {
return try keychain.get(username)
} catch {
MXLog.error("Failed retrieving user restore token")
return nil
}
}
func restoreTokens() -> [KeychainCredentials] {
keychain.allKeys().compactMap { username in
guard let restoreToken = restoreTokenForUsername(username) else {
return nil
}
return KeychainCredentials(userID: username, restoreToken: restoreToken)
}
}
func removeRestoreTokenForUsername(_ username: String) {
do {
try keychain.remove(username)
} catch {
MXLog.error("Failed removing restore token with error: \(error)")
}
}
func removeAllRestoreTokens() {
do {
try keychain.removeAll()
} catch {
MXLog.error("Failed removing all tokens")
}
}
}