diff --git a/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIError.swift b/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIError.swift index e4bab314ece..032927a6be6 100644 --- a/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIError.swift +++ b/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIError.swift @@ -44,6 +44,8 @@ public enum AuthenticationAPIError: Error { case serviceUnavailable + case tooManyRequests(_ message: String, retyAfter: TimeInterval?) + /// Thrown by `requestVerificationCode(for:)`. case invalidEmail @@ -73,7 +75,6 @@ public extension AuthenticationAPIError { case userCreationRestricted case unauthorized - } } diff --git a/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIV0.swift b/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIV0.swift index 030a9dddf20..14a244388af 100644 --- a/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIV0.swift +++ b/WireNetwork/Sources/WireNetwork/APIs/AuthenticationAPI/AuthenticationAPIV0.swift @@ -73,41 +73,51 @@ class AuthenticationAPIV0: AuthenticationAPI, VersionedAPI { for: responseURL ) - let accessToken = try ResponseParser() - .success( - code: .ok, - type: AccessTokenV0.self - ) - .failure( - code: .forbidden, - label: "code-authentication-required", - error: AuthenticationAPIError.twoFactorAuthenticationRequired - ) - .failure( - code: .forbidden, - label: "code-authentication-failed", - error: AuthenticationAPIError.twoFactorAuthenticationFailed - ) - .failure( - code: .forbidden, - label: "pending-activation", - error: AuthenticationAPIError.accountPendingActivation - ) - .failure( - code: .forbidden, - label: "suspended", - error: AuthenticationAPIError.accountSuspended - ) - .failure( - code: .forbidden, - label: "invalid-credentials", - error: AuthenticationAPIError.invalidCredentials - ) - .parse( - code: response.statusCode, - data: data - ) - + let accessToken: AccessToken + do { + accessToken = try ResponseParser() + .success( + code: .ok, + type: AccessTokenV0.self + ) + .failure( + code: .forbidden, + label: "code-authentication-required", + error: AuthenticationAPIError.twoFactorAuthenticationRequired + ) + .failure( + code: .forbidden, + label: "code-authentication-failed", + error: AuthenticationAPIError.twoFactorAuthenticationFailed + ) + .failure( + code: .forbidden, + label: "pending-activation", + error: AuthenticationAPIError.accountPendingActivation + ) + .failure( + code: .forbidden, + label: "suspended", + error: AuthenticationAPIError.accountSuspended + ) + .failure( + code: .forbidden, + label: "invalid-credentials", + error: AuthenticationAPIError.invalidCredentials + ) + .failure(code: .tooManyRequests, decodableError: FailureResponseV0.self) + .parse( + code: response.statusCode, + data: data + ) + } catch let error as FailureResponseV0 { + if error.code == HTTPStatusCode.tooManyRequests.rawValue && error.label == "client-error" { + let retryAfter = responseHeaders["retry-after"].flatMap { TimeInterval($0) } + throw AuthenticationAPIError.tooManyRequests(error.message, retyAfter: retryAfter) + } + throw error + } + return (cookies, accessToken) } diff --git a/WireNetwork/Sources/WireNetwork/HTTP Client/HTTPStatusCode.swift b/WireNetwork/Sources/WireNetwork/HTTP Client/HTTPStatusCode.swift index 2abc08e2b34..f00c6a4d4e5 100644 --- a/WireNetwork/Sources/WireNetwork/HTTP Client/HTTPStatusCode.swift +++ b/WireNetwork/Sources/WireNetwork/HTTP Client/HTTPStatusCode.swift @@ -61,6 +61,10 @@ enum HTTPStatusCode: Int { /// conflict - 409 case conflict = 409 + + // too many requests - 429 + + case tooManyRequests = 429 /// domain blocked - 451 diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift index c7c7202f885..25cf13ed114 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift @@ -69,16 +69,18 @@ extension ZMUserSession { func close(deleteCookie: Bool, completion: @escaping () -> Void) { // Clear all notifications associated with the account from the notification center - syncManagedObjectContext.performGroupedBlock { - self.localNotificationDispatcher?.cancelAllNotifications() + + syncManagedObjectContext.performAndWait { [weak self] in + self?.syncManagedObjectContext.disableSaves() + self?.localNotificationDispatcher?.cancelAllNotifications() } if deleteCookie { deleteUserKeychainItems() } - syncManagedObjectContext.perform { - self.tearDown() + syncManagedObjectContext.performAndWait { [weak self] in + self?.tearDown() } completion() diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift index e37528176d1..0553645b320 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift @@ -655,7 +655,9 @@ public final class ZMUserSession: NSObject { operationLoop = nil transportSession.tearDown() notificationDispatcher.tearDown() - callCenter?.tearDown() + managedObjectContext.performAndWait { [weak self] in + self?.callCenter?.tearDown() + } coreDataStack.close() contextStorage.clear() diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Helpers/LogOutHelper.swift b/wire-ios/Wire-iOS/Sources/Authentication/Helpers/LogOutHelper.swift index bdf402cf088..b6d50dd7b17 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Helpers/LogOutHelper.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Helpers/LogOutHelper.swift @@ -72,7 +72,7 @@ final class LogOutHelper { return viewController } - private func logout(password: String? = nil) { + func logout(password: String? = nil) { guard let selfUser = ZMUser.selfUser() else { return } if selfUser.usesCompanyLogin || password != nil { diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift index c041712a513..496ed68b4ca 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift @@ -85,7 +85,9 @@ final class DeveloperDebugActionsViewModel: ObservableObject { .init(title: "Clear collapsed messages cache", action: clearCollapsedMessagesCache), .init(title: "Simulate access token failure", action: simulateAccessTokenFailure), .init(title: "Invalidate all conversations", action: invalidateAllConversations), - .init(title: "Set last app version migration", action: requestAppVersionInput) + .init(title: "Set last app version migration", action: requestAppVersionInput), + .init(title: "Logout", action: logout) + ] let toggleItems: [DeveloperDebugActionsDisplayModel.ToggleItem] = [ @@ -147,6 +149,10 @@ final class DeveloperDebugActionsViewModel: ObservableObject { // MARK: - Forces logout + func logout() { + LogOutHelper(showLoading: {}, hideLoading: {}).logout() + } + private func simulateAccessTokenFailure() { guard let selfUserID = userSession?.managedObjectContext.performAndWait({ userSession?.selfUser.remoteIdentifier