Password reset fixes

This commit is contained in:
Ali 2021-07-06 03:27:53 +04:00
parent ad5d4f5c75
commit a2f0f5b1ce
15 changed files with 4885 additions and 4972 deletions

View File

@ -6549,3 +6549,4 @@ Sorry for the inconvenience.";
"TwoStepAuth.CancelResetTitle" = "Cancel Reset";
"TwoStepAuth.ResetAction" = "Reset Password";
"TwoStepAuth.CancelResetText" = "Cancel the password resetting process? If you proceed, the expired part of the 7-day delay will be lost.";
"TwoStepAuth.RecoveryEmailResetNoAccess" = "No access";

View File

@ -686,7 +686,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss()
}
switch update {
case .noPassword, .awaitingEmailConfirmation:
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break
case .passwordSet:
var updatedToken = webToken
@ -754,7 +754,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss()
}
switch update {
case .noPassword, .awaitingEmailConfirmation:
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break
case .passwordSet:
var updatedToken = webToken
@ -810,7 +810,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss()
}
switch update {
case .noPassword, .awaitingEmailConfirmation:
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break
case .passwordSet:
var updatedToken = token

View File

@ -558,7 +558,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable
return
}
switch update {
case .noPassword:
case .noPassword, .pendingPasswordReset:
strongSelf.updateState(animated: false, { state in
var state = state
if let verificationState = state.verificationState, case .noChallenge = verificationState {

View File

@ -179,7 +179,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
state.checking = true
return state
}
saveDisposable.set((context.engine.auth.recoverTwoStepVerificationPassword(code: state.code)
saveDisposable.set((context.engine.auth.performPasswordRecovery(code: state.code, updatedPassword: .none)
|> deliverOnMainQueue).start(error: { error in
updateState { state in
var state = state
@ -190,7 +190,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
switch error {
case .invalidCode:
text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid
case .codeExpired:
case .expired:
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError

View File

@ -138,6 +138,7 @@ public enum SetupTwoStepVerificationStateUpdate {
case noPassword
case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?)
case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool)
case pendingPasswordReset
}
final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {

View File

@ -14,18 +14,27 @@ import AnimatedStickerNode
public enum TwoFactorDataInputMode {
public struct Recovery {
public var code: String
public var syncContacts: Bool
public var account: UnauthorizedAccount
public init(code: String, syncContacts: Bool, account: UnauthorizedAccount) {
self.code = code
self.syncContacts = syncContacts
self.account = account
public enum Mode {
case notAuthorized(syncContacts: Bool)
case authorized
}
public var code: String
public var mode: Mode
public init(code: String, mode: Mode) {
self.code = code
self.mode = mode
}
}
public enum PasswordRecoveryEmailMode {
case notAuthorized(syncContacts: Bool)
case authorized
}
case password
case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode)
case passwordRecovery(Recovery)
case emailAddress(password: String, hint: String)
case updateEmailAddress(password: String)
@ -39,8 +48,9 @@ public final class TwoFactorDataInputScreen: ViewController {
private var presentationData: PresentationData
private let mode: TwoFactorDataInputMode
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
private let actionDisposable = MetaDisposable()
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void) {
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
self.sharedContext = sharedContext
self.engine = engine
self.mode = mode
@ -54,7 +64,7 @@ public final class TwoFactorDataInputScreen: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.navigationPresentation = presentation
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
@ -65,6 +75,10 @@ public final class TwoFactorDataInputScreen: ViewController {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.actionDisposable.dispose()
}
@objc private func backPressed() {
self.dismiss()
}
@ -101,8 +115,57 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: nil, password: values[0]), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: nil, password: values[0]), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
navigationController.setViewControllers(controllers, animated: true)
case let .passwordRecoveryEmail(_, mode):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return
}
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
strongSelf.actionDisposable.set((strongSelf.engine.auth.checkPasswordRecoveryCode(code: text)
|> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let text: String
switch error {
case .limitExceeded:
text = strongSelf.presentationData.strings.LoginPassword_FloodError
case .invalidCode:
text = strongSelf.presentationData.strings.Login_InvalidCodeError
case .expired:
text = strongSelf.presentationData.strings.Login_CodeExpiredError
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak statusController] in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let mappedMode: TwoFactorDataInputMode.Recovery.Mode
switch mode {
case .authorized:
mappedMode = .authorized
case let .notAuthorized(syncContacts):
mappedMode = .notAuthorized(syncContacts: syncContacts)
}
let setupController = TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordRecovery(TwoFactorDataInputMode.Recovery(code: text, mode: mappedMode)), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation)
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
navigationController.replaceController(strongSelf, with: setupController, animated: true)
}))
case let .passwordRecovery(recovery):
let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
if values.count != 2 {
@ -120,17 +183,7 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorAuthSplashScreen {
return false
}
if controller is TwoFactorDataInputScreen && controller !== strongSelf {
return false
}
return true
}
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0]), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
navigationController.replaceController(strongSelf, with: TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0]), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation), animated: true)
case let .emailAddress(password, hint):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return
@ -163,7 +216,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
navigationController.setViewControllers(controllers, animated: true)
} else {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -337,7 +390,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
} else {
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated))
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
}
}
}, skipAction: { [weak self] in
@ -405,7 +458,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
} else {
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated))
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
}
case let .passwordRecovery(recovery):
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertText, actions: [
@ -439,10 +492,32 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
navigationController.setViewControllers(controllers, animated: true)
} else {
}
case .passwordRecoveryEmail:
switch strongSelf.engine {
case let .authorized(engine):
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
let _ = (engine.auth.requestTwoStepPasswordReset()
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case .done, .waitingForReset:
strongSelf.stateUpdated(.pendingPasswordReset)
case .declined:
break
case let .error(reason):
break
}
})
})]), in: .window(.root))
case .unauthorized:
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
default:
break
}
@ -450,11 +525,38 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let strongSelf = self else {
return
}
switch strongSelf.mode {
case .passwordRecoveryEmail:
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (strongSelf.engine.auth.resendTwoStepRecoveryEmail()
let _ = (strongSelf.engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let text: String
switch error {
case .limitExceeded:
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak statusController] in
statusController?.dismiss()
})
default:
guard case let .authorized(engine) = strongSelf.engine else {
return
}
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (engine.auth.resendTwoStepRecoveryEmail()
|> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss()
@ -473,6 +575,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}, completed: { [weak statusController] in
statusController?.dismiss()
})
}
})
self.displayNodeDidLoad()
@ -485,13 +588,58 @@ public final class TwoFactorDataInputScreen: ViewController {
}
private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) {
guard case let .unauthorized(engine) = self.engine else {
return
}
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
self.present(statusController, in: .window(.root))
let _ = (engine.auth.performPasswordRecovery(accountManager: self.sharedContext.accountManager, code: recovery.code, syncContacts: recovery.syncContacts, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
switch self.engine {
case let .unauthorized(engine):
var syncContacts = false
switch recovery.mode {
case let .notAuthorized(syncContactsValue):
syncContacts = syncContactsValue
case .authorized:
break
}
let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(next: { [weak self, weak statusController] recoveredAccountData in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
if password.isEmpty {
strongSelf.stateUpdated(.noPassword)
} else {
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
}
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts)), animated: true)
}, error: { [weak self, weak statusController] error in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let text: String
switch error {
case .limitExceeded:
text = strongSelf.presentationData.strings.LoginPassword_FloodError
case .invalidCode:
text = strongSelf.presentationData.strings.Login_InvalidCodeError
case .expired:
text = strongSelf.presentationData.strings.Login_CodeExpiredError
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak self, weak statusController] in
statusController?.dismiss()
})
case let .authorized(engine):
let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in
statusController?.dismiss()
@ -519,10 +667,16 @@ public final class TwoFactorDataInputScreen: ViewController {
return
}
if password.isEmpty {
strongSelf.stateUpdated(.noPassword)
} else {
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
}
strongSelf.dismiss()
})
}
}
}
private enum TwoFactorDataInputTextNodeType {
case password(confirmation: Bool)
@ -805,7 +959,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.scrollNode.canCancelAllTouchesInViews = true
switch mode {
case .password, .passwordRecovery, .emailAddress, .updateEmailAddress:
case .password, .passwordRecovery, .passwordRecoveryEmail, .emailAddress, .updateEmailAddress:
self.monkeyNode = ManagedMonkeyAnimationNode()
case .emailConfirmation:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
@ -909,6 +1063,33 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node)
}),
]
case let .passwordRecoveryEmail(emailPattern, _):
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: rawText, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
for (_, range) in ranges {
string.addAttribute(.font, value: Font.semibold(16.0), range: range)
}
text = string
buttonText = presentationData.strings.TwoFactorSetup_EmailVerification_Action
skipActionText = ""
changeEmailActionText = presentationData.strings.TwoStepAuth_RecoveryEmailResetNoAccess
resendCodeActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ResendAction
inputNodes = [
TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focusUpdated: { node, focused in
focusUpdated?(node, focused)
}, next: { node in
next?(node)
}, updated: { node in
updated?(node)
}, toggleTextHidden: { node in
toggleTextHidden?(node)
}),
]
case let .emailConfirmation(_, emailPattern, _):
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)
@ -985,7 +1166,6 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.changeEmailActionTitleNode.attributedText = NSAttributedString(string: changeEmailActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
self.changeEmailActionButtonNode = HighlightTrackingButtonNode()
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.resendCodeActionTitleNode = ImmediateTextNode()
self.resendCodeActionTitleNode.isUserInteractionEnabled = false
@ -1129,7 +1309,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
break
}
}
focusUpdated = { [weak self] node, _ in
focusUpdated = { node, _ in
DispatchQueue.main.async {
updateAnimations()
}
@ -1156,6 +1336,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
if let codeLength = codeLength, text.count == codeLength {
action()
}
case .passwordRecoveryEmail:
let text = strongSelf.inputNodes[0].text
let hasText = !text.isEmpty
strongSelf.buttonNode.isHidden = !hasText
strongSelf.changeEmailActionTitleNode.isHidden = hasText
strongSelf.changeEmailActionButtonNode.isHidden = hasText
strongSelf.resendCodeActionTitleNode.isHidden = hasText
strongSelf.resendCodeActionButtonNode.isHidden = hasText
if text.count == 6 {
action()
}
case .passwordHint:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
strongSelf.buttonNode.isHidden = !hasText

View File

@ -15,6 +15,7 @@ import TelegramCore
public enum TwoFactorAuthSplashMode {
case intro
case done
case recoveryDone(recoveredAccountData: RecoveredAccountData, syncContacts: Bool)
}
public final class TwoFactorAuthSplashScreen: ViewController {
@ -23,7 +24,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
private var presentationData: PresentationData
private var mode: TwoFactorAuthSplashMode
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorAuthSplashMode) {
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorAuthSplashMode, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
self.sharedContext = sharedContext
self.engine = engine
self.mode = mode
@ -35,8 +36,9 @@ public final class TwoFactorAuthSplashScreen: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
self.navigationPresentation = presentation
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
@ -58,12 +60,22 @@ public final class TwoFactorAuthSplashScreen: ViewController {
switch strongSelf.mode {
case .intro:
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
}))
}, presentation: strongSelf.navigationPresentation))
case .done:
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
navigationController.filterController(strongSelf, animated: true)
case let .recoveryDone(recoveredAccountData, syncContacts):
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
switch strongSelf.engine {
case let .unauthorized(engine):
let _ = loginWithRecoveredAccountData(accountManager: strongSelf.sharedContext.accountManager, account: engine.account, recoveredAccountData: recoveredAccountData, syncContacts: syncContacts).start()
case .authorized:
navigationController.filterController(strongSelf, animated: true)
}
}
})
@ -124,6 +136,16 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}
case .recoveryDone:
title = self.presentationData.strings.TwoFactorSetup_Done_Title
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)

View File

@ -1,260 +0,0 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import TextFormat
import AccountContext
import AlertUI
import PresentationDataUtils
import Markdown
private final class TwoStepVerificationResetControllerArguments {
let updateEntryText: (String) -> Void
let next: () -> Void
let openEmailInaccessible: () -> Void
init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, openEmailInaccessible: @escaping () -> Void) {
self.updateEntryText = updateEntryText
self.next = next
self.openEmailInaccessible = openEmailInaccessible
}
}
private enum TwoStepVerificationResetSection: Int32 {
case password
}
private enum TwoStepVerificationResetTag: ItemListItemTag {
case input
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? TwoStepVerificationResetTag {
switch self {
case .input:
if case .input = other {
return true
} else {
return false
}
}
} else {
return false
}
}
}
private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
case codeEntry(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String)
var section: ItemListSectionId {
return TwoStepVerificationResetSection.password.rawValue
}
var stableId: Int32 {
switch self {
case .codeEntry:
return 0
case .codeInfo:
return 1
}
}
static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
switch lhs {
case let .codeEntry(lhsTheme, lhsStrings, lhsPlaceholder, lhsText):
if case let .codeEntry(rhsTheme, rhsStrings, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
return true
} else {
return false
}
case let .codeInfo(lhsTheme, lhsText):
if case let .codeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
static func <(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! TwoStepVerificationResetControllerArguments
switch self {
case let .codeEntry(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: placeholder, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationResetTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText)
}, action: {
arguments.next()
})
case let .codeInfo(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
}
}
}
private struct TwoStepVerificationResetControllerState: Equatable {
let codeText: String
let checking: Bool
init(codeText: String, checking: Bool) {
self.codeText = codeText
self.checking = checking
}
static func ==(lhs: TwoStepVerificationResetControllerState, rhs: TwoStepVerificationResetControllerState) -> Bool {
if lhs.codeText != rhs.codeText {
return false
}
if lhs.checking != rhs.checking {
return false
}
return true
}
func withUpdatedCodeText(_ codeText: String) -> TwoStepVerificationResetControllerState {
return TwoStepVerificationResetControllerState(codeText: codeText, checking: self.checking)
}
func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationResetControllerState {
return TwoStepVerificationResetControllerState(codeText: self.codeText, checking: checking)
}
}
private func twoStepVerificationResetControllerEntries(presentationData: PresentationData, state: TwoStepVerificationResetControllerState, emailPattern: String) -> [TwoStepVerificationResetEntry] {
var entries: [TwoStepVerificationResetEntry] = []
entries.append(.codeEntry(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.codeText))
entries.append(.codeInfo(presentationData.theme, "\(presentationData.strings.TwoStepAuth_RecoveryCodeHelp)\n\n[\(presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(escapedPlaintextForMarkdown(emailPattern)).0)]()"))
return entries
}
func twoStepVerificationResetController(context: AccountContext, emailPattern: String, result: Promise<Bool>, requestedRecoveryReset: @escaping () -> Void) -> ViewController {
let initialState = TwoStepVerificationResetControllerState(codeText: "", checking: false)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((TwoStepVerificationResetControllerState) -> TwoStepVerificationResetControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments) -> Void)?
let actionsDisposable = DisposableSet()
let resetPasswordDisposable = MetaDisposable()
actionsDisposable.add(resetPasswordDisposable)
let checkCode: () -> Void = {
var code: String?
updateState { state in
if state.checking || state.codeText.isEmpty {
return state
} else {
code = state.codeText
return state.withUpdatedChecking(true)
}
}
if let code = code {
resetPasswordDisposable.set((context.engine.auth.recoverTwoStepVerificationPassword(code: code) |> deliverOnMainQueue).start(error: { error in
updateState {
return $0.withUpdatedChecking(false)
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertText: String
switch error {
case .generic:
alertText = presentationData.strings.Login_UnknownError
case .invalidCode:
alertText = presentationData.strings.Login_InvalidCodeError
case .codeExpired:
alertText = presentationData.strings.Login_CodeExpiredError
case .limitExceeded:
alertText = presentationData.strings.Login_CodeFloodError
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, completed: {
updateState {
return $0.withUpdatedChecking(false)
}
result.set(.single(true))
}))
}
}
let arguments = TwoStepVerificationResetControllerArguments(updateEntryText: { updatedText in
updateState {
$0.withUpdatedCodeText(updatedText)
}
}, next: {
checkCode()
}, openEmailInaccessible: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
let _ = (context.engine.auth.requestTwoStepPasswordReset()
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done, .waitingForReset:
requestedRecoveryReset()
case .declined:
break
case let .error(reason):
break
}
})
})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.checking {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
var nextEnabled = true
if state.codeText.isEmpty {
nextEnabled = false
}
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: nextEnabled, action: {
checkCode()
})
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.TwoStepAuth_RecoveryTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: twoStepVerificationResetControllerEntries(presentationData: presentationData, state: state, emailPattern: emailPattern), style: .blocks, focusItemTag: TwoStepVerificationResetTag.input, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
presentControllerImpl = { [weak controller] c, p in
if let controller = controller {
controller.present(c, in: .window(.root), with: p)
}
}
dismissImpl = { [weak controller] in
controller?.dismiss()
}
return controller
}

View File

@ -293,6 +293,7 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
var replaceControllerImpl: ((ViewController, Bool) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
@ -508,7 +509,27 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
return state
}
var completionImpl: ((Bool) -> Void)?
var stateUpdated: ((SetupTwoStepVerificationStateUpdate) -> Void)?
let controller = TwoFactorDataInputScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .authorized), stateUpdated: { state in
stateUpdated?(state)
})
stateUpdated = { [weak controller] state in
controller?.view.endEditing(true)
controller?.dismiss()
switch state {
case .noPassword, .awaitingEmailConfirmation, .passwordSet:
controller?.dismiss()
dismissImpl?()
case .pendingPasswordReset:
dataPromise.set(context.engine.auth.twoStepVerificationConfiguration()
|> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: nil))
})
}
}
/*var completionImpl: ((Bool) -> Void)?
let controller = resetPasswordController(context: context, emailPattern: emailPattern, completion: { result in
completionImpl?(result)
})
@ -527,8 +548,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.TwoStepAuth_DisableSuccess, false)), nil)
}
}
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}*/
pushControllerImpl?(controller)
}, error: { _ in
updateState { state in
var state = state
@ -592,6 +613,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .notSet:
let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in
switch update {
case .pendingPasswordReset:
break
case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case let .awaitingEmailConfirmation(password, pattern, codeLength):
@ -623,6 +646,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case let .manage(password, hasRecovery, pendingEmail, hasSecureValues):
let controller = SetupTwoStepVerificationController(context: context, initialState: .updatePassword(current: password, hasRecoveryEmail: hasRecovery, hasSecureValues: hasSecureValues), stateUpdated: { update, shouldDismiss, controller in
switch update {
case .pendingPasswordReset:
break
case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case .awaitingEmailConfirmation:
@ -712,10 +737,10 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .access:
break
case let .manage(password, emailSet, _, hasSecureValues):
//let controller = TwoFactorDataInputScreen(context: context, mode: .updateEmailAddress(password: password))
let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
switch update {
case .pendingPasswordReset:
break
case .noPassword:
assertionFailure()
break
@ -803,6 +828,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
}
let controller = SetupTwoStepVerificationController(context: context, initialState: .confirmEmail(password: password, hasSecureValues: hasSecureValues, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), stateUpdated: { update, shouldDismiss, controller in
switch update {
case .pendingPasswordReset:
break
case .noPassword:
assertionFailure()
break
@ -941,6 +968,9 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
controller.present(c, in: .window(.root), with: p)
}
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
dismissImpl = { [weak controller] in
controller?.dismiss()
}

View File

@ -342,30 +342,6 @@ public enum PasswordRecoveryOption {
case email(pattern: String)
}
public func requestPasswordRecovery(account: UnauthorizedAccount) -> Signal<PasswordRecoveryOption, PasswordRecoveryRequestError> {
return account.network.request(Api.functions.auth.requestPasswordRecovery())
|> map(Optional.init)
|> `catch` { error -> Signal<Api.auth.PasswordRecovery?, PasswordRecoveryRequestError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
return .single(nil)
} else {
return .fail(.generic)
}
}
|> map { result -> PasswordRecoveryOption in
if let result = result {
switch result {
case let .passwordRecovery(emailPattern):
return .email(pattern: emailPattern)
}
} else {
return .none
}
}
}
public enum PasswordRecoveryError {
case invalidCode
case limitExceeded
@ -373,7 +349,7 @@ public enum PasswordRecoveryError {
case generic
}
public func checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
func _internal_checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
@ -389,46 +365,17 @@ public func checkPasswordRecoveryCode(network: Network, code: String) -> Signal<
}
}
func _internal_performPasswordRecovery(accountManager: AccountManager, account: UnauthorizedAccount, code: String, syncContacts: Bool, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<Void, PasswordRecoveryError> {
return _internal_twoStepAuthData(account.network)
|> mapError { _ -> PasswordRecoveryError in
return .generic
public final class RecoveredAccountData {
let authorization: Api.auth.Authorization
init(authorization: Api.auth.Authorization) {
self.authorization = authorization
}
|> mapToSignal { authData -> Signal<Void, PasswordRecoveryError> in
let newSettings: Api.account.PasswordInputSettings?
switch updatedPassword {
case .none:
newSettings = nil
case let .password(password, hint, email):
var flags: Int32 = 1 << 0
if email != nil {
flags |= (1 << 1)
}
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
return .fail(.invalidCode)
}
newSettings = Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: updatedPasswordDerivation.apiAlgo, newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSettings: nil)
}
var flags: Int32 = 0
if newSettings != nil {
flags |= 1 << 0
}
return account.network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_EXPIRED") {
return .expired
} else {
return .invalidCode
}
}
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in
public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
switch recoveredAccountData.authorization {
case let .authorization(_, _, user):
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
@ -443,7 +390,48 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
}
}
|> switchToLatest
|> mapError { _ -> PasswordRecoveryError in }
|> ignoreValues
}
func _internal_performPasswordRecovery(network: Network, code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> PasswordRecoveryError in
return .generic
}
|> mapToSignal { authData -> Signal<RecoveredAccountData, PasswordRecoveryError> in
let newSettings: Api.account.PasswordInputSettings?
switch updatedPassword {
case .none:
newSettings = nil
case let .password(password, hint, email):
var flags: Int32 = 1 << 0
if email != nil {
flags |= (1 << 1)
}
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
return .fail(.invalidCode)
}
newSettings = Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: updatedPasswordDerivation.apiAlgo, newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSettings: nil)
}
var flags: Int32 = 0
if newSettings != nil {
flags |= 1 << 0
}
return network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_EXPIRED") {
return .expired
} else {
return .invalidCode
}
}
|> mapToSignal { result -> Signal<RecoveredAccountData, PasswordRecoveryError> in
return .single(RecoveredAccountData(authorization: result))
}
}
}

View File

@ -24,8 +24,16 @@ public extension TelegramEngineUnauthorized {
return _internal_updateTwoStepVerificationPassword(network: self.account.network, currentPassword: currentPassword, updatedPassword: updatedPassword)
}
public func performPasswordRecovery(accountManager: AccountManager, code: String, syncContacts: Bool, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<Void, PasswordRecoveryError> {
return _internal_performPasswordRecovery(accountManager: accountManager, account: self.account, code: code, syncContacts: syncContacts, updatedPassword: updatedPassword)
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
}
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
}
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
@ -90,8 +98,8 @@ public extension TelegramEngine {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
}
public func recoverTwoStepVerificationPassword(code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> {
return _internal_recoverTwoStepVerificationPassword(network: self.account.network, code: code)
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
}
public func cachedTwoStepPasswordToken() -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
@ -106,6 +114,10 @@ public extension TelegramEngine {
return _internal_requestTemporaryTwoStepPasswordToken(account: self.account, password: password, period: period, requiresBiometrics: requiresBiometrics)
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
}
public func requestTwoStepPasswordReset() -> Signal<RequestTwoStepPasswordResetResult, NoError> {
return _internal_requestTwoStepPasswordReset(network: self.account.network)
}
@ -154,12 +166,21 @@ public extension SomeTelegramEngine {
}
}
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.resendTwoStepRecoveryEmail()
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
case let .unauthorized(engine):
return engine.auth.resendTwoStepRecoveryEmail()
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
}
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.checkPasswordRecoveryCode(code: code)
case let .unauthorized(engine):
return engine.auth.checkPasswordRecoveryCode(code: code)
}
}
}

View File

@ -312,12 +312,19 @@ func _internal_updateTwoStepVerificationEmail(network: Network, currentPassword:
public enum RequestTwoStepVerificationPasswordRecoveryCodeError {
case generic
case limitExceeded
}
func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return network.request(Api.functions.auth.requestPasswordRecovery(), automaticFloodWait: false)
|> mapError { _ -> RequestTwoStepVerificationPasswordRecoveryCodeError in
|> mapError { error -> RequestTwoStepVerificationPasswordRecoveryCodeError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
return .generic
} else {
return .generic
}
}
|> map { result -> String in
switch result {
@ -334,35 +341,6 @@ public enum RecoverTwoStepVerificationPasswordError {
case invalidCode
}
func _internal_recoverTwoStepVerificationPassword(network: Network, code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> RecoverTwoStepVerificationPasswordError in
return .generic
}
|> mapToSignal { authData -> Signal<Void, RecoverTwoStepVerificationPasswordError> in
var flags: Int32 = (1 << 1)
if authData.currentPasswordDerivation != nil {
flags |= (1 << 0)
}
return network.request(Api.functions.auth.recoverPassword(flags: 0, code: code, newSettings: nil), automaticFloodWait: false)
|> mapError { error -> RecoverTwoStepVerificationPasswordError in
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_RECOVERY_EXPIRED" {
return .codeExpired
} else if error.errorDescription == "CODE_INVALID" {
return .invalidCode
} else {
return .generic
}
}
|> mapToSignal { _ -> Signal<Void, RecoverTwoStepVerificationPasswordError> in
return .complete()
}
}
}
func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in
let key = ValueBoxKey(length: 1)

View File

@ -486,26 +486,26 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
controller.forgot = { [weak self, weak controller] in
if let strongSelf = self, let strongController = controller {
strongController.inProgress = true
strongSelf.actionDisposable.set((requestPasswordRecovery(account: strongSelf.account)
|> deliverOnMainQueue).start(next: { option in
strongSelf.actionDisposable.set((TelegramEngineUnauthorized(account: strongSelf.account).auth.requestTwoStepVerificationPasswordRecoveryCode()
|> deliverOnMainQueue).start(next: { pattern in
if let strongSelf = self, let strongController = controller {
strongController.inProgress = false
switch option {
case let .email(pattern):
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordEntry(hint, number, code, _, syncContacts) = state.contents {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordRecovery(hint: hint, number: number, code: code, emailPattern: pattern, syncContacts: syncContacts)))
}
}).start()
case .none:
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.didForgotWithNoRecovery = true
}
}
}, error: { error in
if let strongController = controller {
strongController.inProgress = false
guard let strongController = controller else {
return
}
strongController.inProgress = false
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.didForgotWithNoRecovery = true
}))
}
}
@ -542,82 +542,21 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
return controller
}
private func passwordRecoveryController(emailPattern: String, syncContacts: Bool) -> AuthorizationSequencePasswordRecoveryController {
var currentController: AuthorizationSequencePasswordRecoveryController?
private func passwordRecoveryController(emailPattern: String, syncContacts: Bool) -> TwoFactorDataInputScreen {
var currentController: TwoFactorDataInputScreen?
for c in self.viewControllers {
if let c = c as? AuthorizationSequencePasswordRecoveryController {
if let c = c as? TwoFactorDataInputScreen {
currentController = c
break
}
}
let controller: AuthorizationSequencePasswordRecoveryController
let controller: TwoFactorDataInputScreen
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequencePasswordRecoveryController(strings: self.presentationData.strings, theme: self.presentationData.theme, back: { [weak self] in
guard let strongSelf = self else {
return
controller = TwoFactorDataInputScreen(sharedContext: self.sharedContext, engine: .unauthorized(TelegramEngineUnauthorized(account: self.account)), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .notAuthorized(syncContacts: syncContacts)), stateUpdated: { _ in
}, presentation: .default)
}
let countryCode = defaultCountryCode()
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
}).start()
})
controller.recoverWithCode = { [weak self, weak controller] code in
guard let strongSelf = self else {
return
}
controller?.inProgress = true
strongSelf.actionDisposable.set((checkPasswordRecoveryCode(network: strongSelf.account.network, code: code)
|> deliverOnMainQueue).start(error: { error in
guard let strongSelf = self, let controller = controller else {
return
}
controller.inProgress = false
let text: String
switch error {
case .limitExceeded:
text = strongSelf.presentationData.strings.LoginPassword_FloodError
case .invalidCode:
text = strongSelf.presentationData.strings.Login_InvalidCodeError
case .expired:
text = strongSelf.presentationData.strings.Login_CodeExpiredError
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: {
guard let strongSelf = self else {
return
}
controller?.inProgress = false
let setupController = TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: .unauthorized(TelegramEngineUnauthorized(account: strongSelf.account)), mode: .passwordRecovery(TwoFactorDataInputMode.Recovery(code: code, syncContacts: syncContacts, account: strongSelf.account)), stateUpdated: { _ in
guard let _ = self else {
return
}
})
strongSelf.setViewControllers(strongSelf.viewControllers + [setupController], animated: true)
}))
}
controller.noAccess = { [weak self, weak controller] in
if let strongSelf = self, let controller = controller {
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let account = strongSelf.account
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordRecovery(hint, number, code, _, syncContacts) = state.contents {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: true, syncContacts: syncContacts)))
}
}).start()
}
}
}
controller.updateData(emailPattern: emailPattern)
return controller
}