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.CancelResetTitle" = "Cancel Reset";
"TwoStepAuth.ResetAction" = "Reset Password"; "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.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() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = webToken var updatedToken = webToken
@ -754,7 +754,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = webToken var updatedToken = webToken
@ -810,7 +810,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = token var updatedToken = token

View File

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

View File

@ -179,7 +179,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
state.checking = true state.checking = true
return state 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 |> deliverOnMainQueue).start(error: { error in
updateState { state in updateState { state in
var state = state var state = state
@ -190,7 +190,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
switch error { switch error {
case .invalidCode: case .invalidCode:
text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid
case .codeExpired: case .expired:
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
case .limitExceeded: case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError text = presentationData.strings.TwoStepAuth_FloodError

View File

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

View File

@ -14,18 +14,27 @@ import AnimatedStickerNode
public enum TwoFactorDataInputMode { public enum TwoFactorDataInputMode {
public struct Recovery { public struct Recovery {
public var code: String public enum Mode {
public var syncContacts: Bool case notAuthorized(syncContacts: Bool)
public var account: UnauthorizedAccount case authorized
public init(code: String, syncContacts: Bool, account: UnauthorizedAccount) {
self.code = code
self.syncContacts = syncContacts
self.account = account
} }
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 password
case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode)
case passwordRecovery(Recovery) case passwordRecovery(Recovery)
case emailAddress(password: String, hint: String) case emailAddress(password: String, hint: String)
case updateEmailAddress(password: String) case updateEmailAddress(password: String)
@ -39,8 +48,9 @@ public final class TwoFactorDataInputScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private let mode: TwoFactorDataInputMode private let mode: TwoFactorDataInputMode
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void 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.sharedContext = sharedContext
self.engine = engine self.engine = engine
self.mode = mode 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))) 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.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout self.navigationPresentation = presentation
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false self.navigationBar?.intrinsicCanTransitionInline = false
@ -65,6 +75,10 @@ public final class TwoFactorDataInputScreen: ViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
self.actionDisposable.dispose()
}
@objc private func backPressed() { @objc private func backPressed() {
self.dismiss() self.dismiss()
} }
@ -101,8 +115,57 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
return true 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) 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): case let .passwordRecovery(recovery):
let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
if values.count != 2 { if values.count != 2 {
@ -120,17 +183,7 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
return return
} }
var controllers = navigationController.viewControllers.filter { controller in 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)
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)
case let .emailAddress(password, hint): case let .emailAddress(password, hint):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else { guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return return
@ -163,7 +216,7 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
return true 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) navigationController.setViewControllers(controllers, animated: true)
} else { } else {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -337,7 +390,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery { if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: value) strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
} else { } 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 }, skipAction: { [weak self] in
@ -405,7 +458,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery { if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: "") strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
} else { } 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): 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: [ 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 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) navigationController.setViewControllers(controllers, animated: true)
} else { } 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: default:
break break
} }
@ -450,11 +525,38 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
switch strongSelf.mode {
case .passwordRecoveryEmail:
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root)) 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 |> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss() statusController?.dismiss()
@ -473,6 +575,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}, completed: { [weak statusController] in }, completed: { [weak statusController] in
statusController?.dismiss() statusController?.dismiss()
}) })
}
}) })
self.displayNodeDidLoad() self.displayNodeDidLoad()
@ -485,13 +588,58 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) { 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)) let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
self.present(statusController, in: .window(.root)) 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 |> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in
statusController?.dismiss() statusController?.dismiss()
@ -519,9 +667,15 @@ public final class TwoFactorDataInputScreen: ViewController {
return return
} }
if password.isEmpty {
strongSelf.stateUpdated(.noPassword)
} else {
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
}
strongSelf.dismiss() strongSelf.dismiss()
}) })
} }
}
} }
private enum TwoFactorDataInputTextNodeType { private enum TwoFactorDataInputTextNodeType {
@ -805,7 +959,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.scrollNode.canCancelAllTouchesInViews = true self.scrollNode.canCancelAllTouchesInViews = true
switch mode { switch mode {
case .password, .passwordRecovery, .emailAddress, .updateEmailAddress: case .password, .passwordRecovery, .passwordRecoveryEmail, .emailAddress, .updateEmailAddress:
self.monkeyNode = ManagedMonkeyAnimationNode() self.monkeyNode = ManagedMonkeyAnimationNode()
case .emailConfirmation: case .emailConfirmation:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") { if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
@ -909,6 +1063,33 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node) 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, _): case let .emailConfirmation(_, emailPattern, _):
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern) 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.changeEmailActionTitleNode.attributedText = NSAttributedString(string: changeEmailActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
self.changeEmailActionButtonNode = HighlightTrackingButtonNode() self.changeEmailActionButtonNode = HighlightTrackingButtonNode()
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.resendCodeActionTitleNode = ImmediateTextNode() self.resendCodeActionTitleNode = ImmediateTextNode()
self.resendCodeActionTitleNode.isUserInteractionEnabled = false self.resendCodeActionTitleNode.isUserInteractionEnabled = false
@ -1129,7 +1309,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
break break
} }
} }
focusUpdated = { [weak self] node, _ in focusUpdated = { node, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
updateAnimations() updateAnimations()
} }
@ -1156,6 +1336,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
if let codeLength = codeLength, text.count == codeLength { if let codeLength = codeLength, text.count == codeLength {
action() 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: case .passwordHint:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty }) let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
strongSelf.buttonNode.isHidden = !hasText strongSelf.buttonNode.isHidden = !hasText

View File

@ -15,6 +15,7 @@ import TelegramCore
public enum TwoFactorAuthSplashMode { public enum TwoFactorAuthSplashMode {
case intro case intro
case done case done
case recoveryDone(recoveredAccountData: RecoveredAccountData, syncContacts: Bool)
} }
public final class TwoFactorAuthSplashScreen: ViewController { public final class TwoFactorAuthSplashScreen: ViewController {
@ -23,7 +24,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var mode: TwoFactorAuthSplashMode 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.sharedContext = sharedContext
self.engine = engine self.engine = engine
self.mode = mode 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))) 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.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false self.navigationBar?.intrinsicCanTransitionInline = false
@ -58,12 +60,22 @@ public final class TwoFactorAuthSplashScreen: ViewController {
switch strongSelf.mode { switch strongSelf.mode {
case .intro: case .intro:
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
})) }, presentation: strongSelf.navigationPresentation))
case .done: case .done:
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
return return
} }
navigationController.filterController(strongSelf, animated: true) 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) text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action 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") { 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.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0) 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 replaceControllerImpl: ((ViewController, Bool) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -508,7 +509,27 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
return state 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 let controller = resetPasswordController(context: context, emailPattern: emailPattern, completion: { result in
completionImpl?(result) completionImpl?(result)
}) })
@ -527,8 +548,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.TwoStepAuth_DisableSuccess, false)), nil) presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.TwoStepAuth_DisableSuccess, false)), nil)
} }
} }*/
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) pushControllerImpl?(controller)
}, error: { _ in }, error: { _ in
updateState { state in updateState { state in
var state = state var state = state
@ -592,6 +613,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .notSet: case .notSet:
let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil)))) dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case let .awaitingEmailConfirmation(password, pattern, codeLength): case let .awaitingEmailConfirmation(password, pattern, codeLength):
@ -623,6 +646,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case let .manage(password, hasRecovery, pendingEmail, hasSecureValues): 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .updatePassword(current: password, hasRecoveryEmail: hasRecovery, hasSecureValues: hasSecureValues), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil)))) dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case .awaitingEmailConfirmation: case .awaitingEmailConfirmation:
@ -712,10 +737,10 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .access: case .access:
break break
case let .manage(password, emailSet, _, hasSecureValues): 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
assertionFailure() assertionFailure()
break 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .confirmEmail(password: password, hasSecureValues: hasSecureValues, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
assertionFailure() assertionFailure()
break break
@ -941,6 +968,9 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
controller.present(c, in: .window(.root), with: p) controller.present(c, in: .window(.root), with: p)
} }
} }
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismiss() controller?.dismiss()
} }

View File

@ -342,30 +342,6 @@ public enum PasswordRecoveryOption {
case email(pattern: String) 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 { public enum PasswordRecoveryError {
case invalidCode case invalidCode
case limitExceeded case limitExceeded
@ -373,7 +349,7 @@ public enum PasswordRecoveryError {
case generic 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) return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in |> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { 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> { public final class RecoveredAccountData {
return _internal_twoStepAuthData(account.network) let authorization: Api.auth.Authorization
|> mapError { _ -> PasswordRecoveryError in
return .generic
}
|> 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 { init(authorization: Api.auth.Authorization) {
return .fail(.invalidCode) self.authorization = authorization
} }
}
newSettings = Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: updatedPasswordDerivation.apiAlgo, newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSettings: nil) public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal<Never, NoError> {
}
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
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result { switch recoveredAccountData.authorization {
case let .authorization(_, _, user): case let .authorization(_, _, user):
let user = TelegramUser(user: user) let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) 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 |> 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) 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> { public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return _internal_performPasswordRecovery(accountManager: accountManager, account: self.account, code: code, syncContacts: syncContacts, updatedPassword: updatedPassword) 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> { public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
@ -90,8 +98,8 @@ public extension TelegramEngine {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network) return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
} }
public func recoverTwoStepVerificationPassword(code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> { public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_recoverTwoStepVerificationPassword(network: self.account.network, code: code) return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
} }
public func cachedTwoStepPasswordToken() -> Signal<TemporaryTwoStepPasswordToken?, NoError> { 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) 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> { public func requestTwoStepPasswordReset() -> Signal<RequestTwoStepPasswordResetResult, NoError> {
return _internal_requestTwoStepPasswordReset(network: self.account.network) 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 { switch self.engine {
case let .authorized(engine): case let .authorized(engine):
return engine.auth.resendTwoStepRecoveryEmail() return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
case let .unauthorized(engine): 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 { public enum RequestTwoStepVerificationPasswordRecoveryCodeError {
case generic case generic
case limitExceeded
} }
func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> { func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return network.request(Api.functions.auth.requestPasswordRecovery(), automaticFloodWait: false) 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 return .generic
} else {
return .generic
}
} }
|> map { result -> String in |> map { result -> String in
switch result { switch result {
@ -334,35 +341,6 @@ public enum RecoverTwoStepVerificationPasswordError {
case invalidCode 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> { func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in
let key = ValueBoxKey(length: 1) let key = ValueBoxKey(length: 1)

View File

@ -486,26 +486,26 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
controller.forgot = { [weak self, weak controller] in controller.forgot = { [weak self, weak controller] in
if let strongSelf = self, let strongController = controller { if let strongSelf = self, let strongController = controller {
strongController.inProgress = true strongController.inProgress = true
strongSelf.actionDisposable.set((requestPasswordRecovery(account: strongSelf.account) strongSelf.actionDisposable.set((TelegramEngineUnauthorized(account: strongSelf.account).auth.requestTwoStepVerificationPasswordRecoveryCode()
|> deliverOnMainQueue).start(next: { option in |> deliverOnMainQueue).start(next: { pattern in
if let strongSelf = self, let strongController = controller { if let strongSelf = self, let strongController = controller {
strongController.inProgress = false strongController.inProgress = false
switch option {
case let .email(pattern):
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordEntry(hint, number, code, _, syncContacts) = state.contents { 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))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordRecovery(hint: hint, number: number, code: code, emailPattern: pattern, syncContacts: syncContacts)))
} }
}).start() }).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 }, error: { error in
if let strongController = controller { guard let strongController = controller else {
strongController.inProgress = false 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 return controller
} }
private func passwordRecoveryController(emailPattern: String, syncContacts: Bool) -> AuthorizationSequencePasswordRecoveryController { private func passwordRecoveryController(emailPattern: String, syncContacts: Bool) -> TwoFactorDataInputScreen {
var currentController: AuthorizationSequencePasswordRecoveryController? var currentController: TwoFactorDataInputScreen?
for c in self.viewControllers { for c in self.viewControllers {
if let c = c as? AuthorizationSequencePasswordRecoveryController { if let c = c as? TwoFactorDataInputScreen {
currentController = c currentController = c
break break
} }
} }
let controller: AuthorizationSequencePasswordRecoveryController let controller: TwoFactorDataInputScreen
if let currentController = currentController { if let currentController = currentController {
controller = currentController controller = currentController
} else { } else {
controller = AuthorizationSequencePasswordRecoveryController(strings: self.presentationData.strings, theme: self.presentationData.theme, back: { [weak self] in controller = TwoFactorDataInputScreen(sharedContext: self.sharedContext, engine: .unauthorized(TelegramEngineUnauthorized(account: self.account)), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .notAuthorized(syncContacts: syncContacts)), stateUpdated: { _ in
guard let strongSelf = self else { }, presentation: .default)
return
} }
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 return controller
} }