Initial implementation of password restore

This commit is contained in:
Ali 2021-06-22 01:26:52 +04:00
parent 50c4e6c031
commit 77e55e973c
15 changed files with 5233 additions and 4937 deletions

View File

@ -6528,3 +6528,13 @@ Sorry for the inconvenience.";
"Settings.CheckPasswordText" = "Your account is protected by 2-Step Verification. Do you still remember your password?";
"Settings.KeepPassword" = "Yes, definitely";
"Settings.TryEnterPassword" = "Not sure, let me try";
"TwoFactorSetup.PasswordRecovery.Title" = "Create New Password";
"TwoFactorSetup.PasswordRecovery.Text" = "You have successfully reset your password.\nPlease enter a new password to continue";
"TwoFactorSetup.PasswordRecovery.PlaceholderPassword" = "New Password";
"TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password";
"TwoFactorSetup.PasswordRecovery.Action" = "Continue";
"TwoFactorSetup.PasswordRecovery.Skip" = "Skip";
"TwoFactorSetup.PasswordRecovery.SkipAlertTitle" = "Attention!";
"TwoFactorSetup.PasswordRecovery.SkipAlertText" = "Skipping this step will disable 2-step verification for your account. Are you sure you want to skip?";
"TwoFactorSetup.PasswordRecovery.SkipAlertAction" = "Skip";

View File

@ -11,7 +11,9 @@ import ProgressNavigationButtonNode
import AccountContext
public class SetupTwoStepVerificationController: ViewController {
private let context: AccountContext
private let network: Network
private let sharedContext: SharedAccountContext
private let initialState: SetupTwoStepVerificationInitialState
private let stateUpdated: (SetupTwoStepVerificationStateUpdate, Bool, SetupTwoStepVerificationController) -> Void
@ -32,12 +34,18 @@ public class SetupTwoStepVerificationController: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, initialState: SetupTwoStepVerificationInitialState, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool, SetupTwoStepVerificationController) -> Void) {
self.context = context
convenience public init(context: AccountContext, initialState: SetupTwoStepVerificationInitialState, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool, SetupTwoStepVerificationController) -> Void) {
self.init(sharedContext: context.sharedContext, network: context.account.network, initialState: initialState, stateUpdated: stateUpdated)
}
public init(sharedContext: SharedAccountContext, network: Network, initialState: SetupTwoStepVerificationInitialState, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool, SetupTwoStepVerificationController) -> Void) {
self.sharedContext = sharedContext
self.network = network
self.initialState = initialState
self.stateUpdated = stateUpdated
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
@ -45,7 +53,7 @@ public class SetupTwoStepVerificationController: ViewController {
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
self.presentationDataDisposable = (context.sharedContext.presentationData
self.presentationDataDisposable = (self.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
@ -95,7 +103,7 @@ public class SetupTwoStepVerificationController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = SetupTwoStepVerificationControllerNode(context: self.context, updateBackAction: { [weak self] action in
self.displayNode = SetupTwoStepVerificationControllerNode(sharedContext: self.sharedContext, network: self.network, updateBackAction: { [weak self] action in
guard let strongSelf = self else {
return
}

View File

@ -141,7 +141,8 @@ public enum SetupTwoStepVerificationStateUpdate {
}
final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
private let context: AccountContext
private let sharedContext: SharedAccountContext
private let network: Network
private var presentationData: PresentationData
private let updateBackAction: (Bool) -> Void
private let updateNextAction: (SetupTwoStepVerificationNextAction) -> Void
@ -154,14 +155,15 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
private var contentNode: SetupTwoStepVerificationContentNode?
private let actionDisposable = MetaDisposable()
init(context: AccountContext, updateBackAction: @escaping (Bool) -> Void, updateNextAction: @escaping (SetupTwoStepVerificationNextAction) -> Void, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, initialState: SetupTwoStepVerificationInitialState) {
self.context = context
init(sharedContext: SharedAccountContext, network: Network, updateBackAction: @escaping (Bool) -> Void, updateNextAction: @escaping (SetupTwoStepVerificationNextAction) -> Void, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, initialState: SetupTwoStepVerificationInitialState) {
self.sharedContext = sharedContext
self.network = network
self.updateBackAction = updateBackAction
self.updateNextAction = updateNextAction
self.stateUpdated = stateUpdated
self.present = present
self.dismiss = dismiss
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
self.innerState = SetupTwoStepVerificationControllerInnerState(layout: nil, data: SetupTwoStepVerificationControllerDataState(activity: false, state: SetupTwoStepVerificationState(initialState: initialState)))
self.activityIndicator = ActivityIndicator(type: .custom(self.presentationData.theme.list.itemAccentColor, 22.0, 2.0, false))
@ -171,7 +173,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
self.processStateUpdated()
if self.innerState.data.state == nil {
self.actionDisposable.set((twoStepAuthData(context.account.network)
self.actionDisposable.set((twoStepAuthData(self.network)
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else {
return
@ -340,7 +342,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
state.data.activity = true
return state
}, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: nil, updatedPassword: .none)
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: nil, updatedPassword: .none)
|> deliverOnMainQueue).start(next: { _ in
guard let strongSelf = self else {
return
@ -356,7 +358,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
state.data.activity = false
@ -393,7 +395,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
state.data.activity = true
return state
}, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.actionDisposable.set((resendTwoStepRecoveryEmail(network: strongSelf.context.account.network)
strongSelf.actionDisposable.set((resendTwoStepRecoveryEmail(network: strongSelf.network)
|> deliverOnMainQueue).start(error: { error in
guard let strongSelf = self else {
return
@ -405,7 +407,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
state.data.activity = false
@ -526,7 +528,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
if password == confirmation {
state.data.state = .enterHint(mode: mode, password: password, hint: "")
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
}
case let .enterHint(mode, password, hint):
switch mode {
@ -534,7 +536,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "")
case let .update(current, hasRecoveryEmail, hasSecureValues):
state.data.activity = true
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: current, updatedPassword: .password(password: password, hint: hint, email: nil))
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: current, updatedPassword: .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
@ -558,7 +560,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
state.data.activity = false
@ -570,7 +572,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
state.data.activity = true
switch enterState {
case let .create(password, hint):
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: nil, updatedPassword: .password(password: password, hint: hint, email: email))
strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: nil, updatedPassword: .password(password: password, hint: hint, email: email))
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
@ -602,7 +604,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
state.data.activity = false
@ -618,7 +620,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
state.data.activity = true
return state
}, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.actionDisposable.set((updateTwoStepVerificationEmail(network: strongSelf.context.account.network, currentPassword: password, updatedEmail: email)
strongSelf.actionDisposable.set((updateTwoStepVerificationEmail(network: strongSelf.network, currentPassword: password, updatedEmail: email)
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
@ -644,7 +646,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
state.data.activity = false
@ -654,7 +656,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
}
case let .confirmEmail(confirmState, _, _, code):
state.data.activity = true
strongSelf.actionDisposable.set((confirmTwoStepRecoveryEmail(network: strongSelf.context.account.network, code: code)
strongSelf.actionDisposable.set((confirmTwoStepRecoveryEmail(network: strongSelf.network, code: code)
|> deliverOnMainQueue).start(error: { error in
guard let strongSelf = self else {
return
@ -673,7 +675,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.updateState({ state in
var state = state
@ -697,8 +699,8 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
return state
}, transition: .animated(duration: 0.5, curve: .spring))
}
if case let .enterEmail(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty {
self.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: {
if case let .enterEmail(enterEmailState, enterEmailEmail)? = self.innerState.data.state, case .create = enterEmailState, enterEmailEmail.isEmpty {
self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: {
continueImpl()
})]), nil)
} else {

View File

@ -13,25 +13,40 @@ import TelegramCore
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
}
}
case password
case passwordRecovery(Recovery)
case emailAddress(password: String, hint: String)
case updateEmailAddress(password: String)
case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?)
case passwordHint(password: String)
case passwordHint(recovery: Recovery?, password: String)
}
public final class TwoFactorDataInputScreen: ViewController {
private let context: AccountContext
private let sharedContext: SharedAccountContext
private let network: Network
private var presentationData: PresentationData
private let mode: TwoFactorDataInputMode
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
public init(context: AccountContext, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void) {
self.context = context
public init(sharedContext: SharedAccountContext, network: Network, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void) {
self.sharedContext = sharedContext
self.network = network
self.mode = mode
self.stateUpdated = stateUpdated
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
@ -86,7 +101,35 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .passwordHint(password: values[0]), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .passwordHint(recovery: nil, password: values[0]), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
case let .passwordRecovery(recovery):
let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
if values.count != 2 {
return
}
if values[0] != values[1] {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
return
}
if values[0].isEmpty {
return
}
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, network: strongSelf.network, mode: .passwordHint(recovery: recovery, password: values[0]), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
case let .emailAddress(password, hint):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
@ -95,7 +138,7 @@ public final class TwoFactorDataInputScreen: ViewController {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: text))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: text))
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
@ -120,7 +163,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -135,7 +178,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(context: strongSelf.context, mode: .done))
controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}
}
@ -154,7 +197,7 @@ public final class TwoFactorDataInputScreen: ViewController {
case .invalidEmail:
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
case let .updateEmailAddress(password):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
@ -163,7 +206,7 @@ public final class TwoFactorDataInputScreen: ViewController {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationEmail(network: strongSelf.context.account.network, currentPassword: password, updatedEmail: text)
let _ = (updateTwoStepVerificationEmail(network: strongSelf.network, currentPassword: password, updatedEmail: text)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
@ -188,7 +231,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailConfirmation(passwordAndHint: (password, ""), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .emailConfirmation(passwordAndHint: (password, ""), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -203,7 +246,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(context: strongSelf.context, mode: .done))
controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}
}
@ -222,7 +265,7 @@ public final class TwoFactorDataInputScreen: ViewController {
case .invalidEmail:
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
case .emailConfirmation:
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
@ -231,7 +274,7 @@ public final class TwoFactorDataInputScreen: ViewController {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (confirmTwoStepRecoveryEmail(network: strongSelf.context.account.network, code: text)
let _ = (confirmTwoStepRecoveryEmail(network: strongSelf.network, code: text)
|> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss()
@ -253,7 +296,7 @@ public final class TwoFactorDataInputScreen: ViewController {
case .generic:
text = presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak statusController] in
statusController?.dismiss()
@ -273,15 +316,19 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(context: strongSelf.context, mode: .done))
controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
})
case let .passwordHint(password):
case let .passwordHint(recovery, password):
guard let value = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !value.isEmpty else {
return
}
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated))
if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
} else {
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated))
}
}
}, skipAction: { [weak self] in
guard let strongSelf = self else {
@ -297,7 +344,7 @@ public final class TwoFactorDataInputScreen: ViewController {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: nil))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
@ -321,7 +368,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(context: strongSelf.context, mode: .done))
controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}
}, error: { [weak statusController] error in
@ -339,13 +386,27 @@ public final class TwoFactorDataInputScreen: ViewController {
case .invalidEmail:
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
]), in: .window(.root))
case let .passwordHint(password):
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated))
case let .passwordHint(recovery, password):
if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
} else {
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated))
}
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: [
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertAction, action: {
guard let strongSelf = self else {
return
}
strongSelf.performRecovery(recovery: recovery, password: "", hint: "")
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
]), in: .window(.root))
default:
break
}
@ -368,7 +429,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated))
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
}
@ -383,7 +444,7 @@ public final class TwoFactorDataInputScreen: ViewController {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (resendTwoStepRecoveryEmail(network: strongSelf.context.account.network)
let _ = (resendTwoStepRecoveryEmail(network: strongSelf.network)
|> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss()
@ -398,7 +459,7 @@ public final class TwoFactorDataInputScreen: ViewController {
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
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()
})
@ -412,6 +473,42 @@ public final class TwoFactorDataInputScreen: ViewController {
(self.displayNode as! TwoFactorDataInputScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}
private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) {
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
self.present(statusController, in: .window(.root))
let _ = (performPasswordRecovery(accountManager: self.sharedContext.accountManager, account: recovery.account, code: recovery.code, syncContacts: recovery.syncContacts, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(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()
guard let strongSelf = self else {
return
}
strongSelf.dismiss()
})
}
}
private enum TwoFactorDataInputTextNodeType {
@ -695,7 +792,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.scrollNode.canCancelAllTouchesInViews = true
switch mode {
case .password, .emailAddress, .updateEmailAddress:
case .password, .passwordRecovery, .emailAddress, .updateEmailAddress:
self.monkeyNode = ManagedMonkeyAnimationNode()
case .emailConfirmation:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
@ -754,6 +851,33 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node)
})
]
case .passwordRecovery:
title = presentationData.strings.TwoFactorSetup_PasswordRecovery_Title
text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_PasswordRecovery_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
buttonText = presentationData.strings.TwoFactorSetup_PasswordRecovery_Action
skipActionText = presentationData.strings.TwoFactorSetup_PasswordRecovery_Skip
changeEmailActionText = ""
resendCodeActionText = ""
inputNodes = [
TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderPassword, focusUpdated: { node, focused in
focusUpdated?(node, focused)
}, next: { node in
next?(node)
}, updated: { node in
updated?(node)
}, toggleTextHidden: { node in
toggleTextHidden?(node)
}),
TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderConfirmPassword, focusUpdated: { node, focused in
focusUpdated?(node, focused)
}, next: { node in
next?(node)
}, updated: { node in
updated?(node)
}, toggleTextHidden: { node in
toggleTextHidden?(node)
})
]
case .emailAddress, .updateEmailAddress:
title = presentationData.strings.TwoFactorSetup_Email_Title
text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Email_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
@ -937,7 +1061,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
guard let strongSelf = self else {
return
}
if let index = strongSelf.inputNodes.index(where: { $0 === node }) {
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
if index == strongSelf.inputNodes.count - 1 {
strongSelf.action()
} else if strongSelf.buttonNode.isUserInteractionEnabled {
@ -953,14 +1077,12 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
switch strongSelf.mode {
case .password:
if strongSelf.inputNodes[1].isFocused {
let textLength = strongSelf.inputNodes[1].text.count
let maxWidth = strongSelf.inputNodes[1].bounds.width
let textNode = ImmediateTextNode()
textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[1].text, font: Font.regular(17.0), textColor: .black)
let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0))
let maxTextLength = 20
var trackingOffset = textSize.width / maxWidth
trackingOffset = max(0.0, min(1.0, trackingOffset))
strongSelf.monkeyNode?.setState(.tracking(trackingOffset))
@ -978,14 +1100,12 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
}
case .emailAddress:
if strongSelf.inputNodes[0].isFocused {
let textLength = strongSelf.inputNodes[0].text.count
let maxWidth = strongSelf.inputNodes[0].bounds.width
let textNode = ImmediateTextNode()
textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[0].text, font: Font.regular(17.0), textColor: .black)
let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0))
let maxTextLength = 20
var trackingOffset = textSize.width / maxWidth
trackingOffset = max(0.0, min(1.0, trackingOffset))
strongSelf.monkeyNode?.setState(.tracking(trackingOffset))
@ -998,9 +1118,6 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
}
focusUpdated = { [weak self] node, _ in
DispatchQueue.main.async {
guard let strongSelf = self else {
return
}
updateAnimations()
}
}
@ -1031,7 +1148,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
strongSelf.buttonNode.isHidden = !hasText
strongSelf.skipActionTitleNode.isHidden = hasText
strongSelf.skipActionButtonNode.isHidden = hasText
case .password:
case .password, .passwordRecovery:
break
}
updateAnimations()
@ -1113,7 +1230,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: contentAreaSize))
let iconSize: CGSize
if let animatedStickerNode = self.animatedStickerNode {
if let _ = self.animatedStickerNode {
iconSize = CGSize(width: 136.0, height: 136.0)
} else if let monkeyNode = self.monkeyNode {
iconSize = monkeyNode.intrinsicSize
@ -1194,9 +1311,15 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
let buttonFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - buttonWidth) / 2.0), y: max(contentHeight + buttonSpacing, maxButtonY)), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
transition.updateFrame(node: self.skipActionButtonNode, frame: buttonFrame)
transition.updateFrame(node: self.skipActionTitleNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - skipActionSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - skipActionSize.height) / 2.0)), size: skipActionSize))
let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
var skipButtonFrame = buttonFrame
if !self.buttonNode.isHidden {
skipButtonFrame.origin.y += skipButtonFrame.height
}
transition.updateFrame(node: self.skipActionButtonNode, frame: skipButtonFrame)
transition.updateFrame(node: self.skipActionTitleNode, frame: CGRect(origin: CGPoint(x: skipButtonFrame.minX + floor((skipButtonFrame.width - skipActionSize.width) / 2.0), y: skipButtonFrame.minY + floor((skipButtonFrame.height - skipActionSize.height) / 2.0)), size: skipActionSize))
let changeEmailActionFrame: CGRect
let changeEmailActionButtonFrame: CGRect
@ -1222,7 +1345,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
transition.animateView {
self.scrollNode.view.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0)
self.scrollNode.view.contentSize = CGSize(width: contentAreaSize.width, height: max(availableAreaSize.height, buttonFrame.maxY + bottomInset))
self.scrollNode.view.contentSize = CGSize(width: contentAreaSize.width, height: max(availableAreaSize.height, skipButtonFrame.maxY + bottomInset))
}
}
}

View File

@ -10,6 +10,7 @@ import AnimatedStickerNode
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import TelegramCore
public enum TwoFactorAuthSplashMode {
case intro
@ -17,15 +18,17 @@ public enum TwoFactorAuthSplashMode {
}
public final class TwoFactorAuthSplashScreen: ViewController {
private let context: AccountContext
private let sharedContext: SharedAccountContext
private let network: Network
private var presentationData: PresentationData
private var mode: TwoFactorAuthSplashMode
public init(context: AccountContext, mode: TwoFactorAuthSplashMode) {
self.context = context
public init(sharedContext: SharedAccountContext, network: Network, mode: TwoFactorAuthSplashMode) {
self.sharedContext = sharedContext
self.network = network
self.mode = mode
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
@ -48,13 +51,13 @@ public final class TwoFactorAuthSplashScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = TwoFactorAuthSplashScreenNode(context: self.context, presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
self.displayNode = TwoFactorAuthSplashScreenNode(sharedContext: self.sharedContext, presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
guard let strongSelf = self else {
return
}
switch strongSelf.mode {
case .intro:
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .password, stateUpdated: { _ in
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, network: strongSelf.network, mode: .password, stateUpdated: { _ in
}))
case .done:
guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -92,7 +95,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
}
}
init(context: AccountContext, presentationData: PresentationData, mode: TwoFactorAuthSplashMode, action: @escaping () -> Void) {
init(sharedContext: SharedAccountContext, presentationData: PresentationData, mode: TwoFactorAuthSplashMode, action: @escaping () -> Void) {
self.presentationData = presentationData
self.mode = mode
@ -191,9 +194,9 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
var maxContentVerticalOrigin = buttonFrame.minY - 12.0 - contentHeight
let maxContentVerticalOrigin = buttonFrame.minY - 12.0 - contentHeight
contentVerticalOrigin = min(contentVerticalOrigin, maxContentVerticalOrigin)

View File

@ -727,7 +727,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
}
})
}, openTwoStepVerification: { data in
var intro = false
let intro = false
if let data = data {
switch data {
case .set:
@ -735,7 +735,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
case let .notSet(pendingEmail):
//intro = pendingEmail == nil
if pendingEmail == nil {
let controller = TwoFactorAuthSplashScreen(context: context, mode: .intro)
let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, network: context.account.network, mode: .intro)
pushControllerImpl?(controller, true)
return
} else {

View File

@ -795,7 +795,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1908627474] = { return Api.SecureValueType.parse_secureValueTypeEmail($0) }
dict[-732254058] = { return Api.PasswordKdfAlgo.parse_passwordKdfAlgoUnknown($0) }
dict[982592842] = { return Api.PasswordKdfAlgo.parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow($0) }
dict[-1390001672] = { return Api.account.Password.parse_password($0) }
dict[408623183] = { return Api.account.Password.parse_password($0) }
dict[-2000710887] = { return Api.InputBotInlineResult.parse_inputBotInlineResult($0) }
dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) }
dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) }

View File

@ -1325,13 +1325,13 @@ public struct account {
}
public enum Password: TypeConstructorDescription {
case password(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer)
case password(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer, pendingResetDate: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom):
case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate):
if boxed {
buffer.appendInt32(-1390001672)
buffer.appendInt32(408623183)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {currentAlgo!.serialize(buffer, true)}
@ -1342,14 +1342,15 @@ public struct account {
newAlgo.serialize(buffer, true)
newSecureAlgo.serialize(buffer, true)
serializeBytes(secureRandom, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(pendingResetDate!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom):
return ("password", [("flags", flags), ("currentAlgo", currentAlgo), ("srpB", srpB), ("srpId", srpId), ("hint", hint), ("emailUnconfirmedPattern", emailUnconfirmedPattern), ("newAlgo", newAlgo), ("newSecureAlgo", newSecureAlgo), ("secureRandom", secureRandom)])
case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate):
return ("password", [("flags", flags), ("currentAlgo", currentAlgo), ("srpB", srpB), ("srpId", srpId), ("hint", hint), ("emailUnconfirmedPattern", emailUnconfirmedPattern), ("newAlgo", newAlgo), ("newSecureAlgo", newSecureAlgo), ("secureRandom", secureRandom), ("pendingResetDate", pendingResetDate)])
}
}
@ -1378,6 +1379,8 @@ public struct account {
}
var _9: Buffer?
_9 = parseBytes(reader)
var _10: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_10 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
@ -1387,8 +1390,9 @@ public struct account {
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!)
let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10)
}
else {
return nil
@ -5250,11 +5254,13 @@ public extension Api {
})
}
public static func recoverPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.Authorization>) {
public static func recoverPassword(flags: Int32, code: String, newSettings: Api.account.PasswordInputSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.Authorization>) {
let buffer = Buffer()
buffer.appendInt32(1319464594)
buffer.appendInt32(923364464)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(code, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.recoverPassword", parameters: [("code", code)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in
if Int(flags) & Int(1 << 0) != 0 {newSettings!.serialize(buffer, true)}
return (FunctionDescription(name: "auth.recoverPassword", parameters: [("flags", flags), ("code", code), ("newSettings", newSettings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in
let reader = BufferReader(buffer)
var result: Api.auth.Authorization?
if let signature = reader.readInt32() {
@ -5359,6 +5365,20 @@ public extension Api {
return result
})
}
public static func checkRecoveryPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(221691769)
serializeString(code, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.checkRecoveryPassword", parameters: [("code", code)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public struct bots {
public static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.DataJSON>) {
@ -7487,6 +7507,34 @@ public extension Api {
return result
})
}
public static func resetPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1517436040)
return (FunctionDescription(name: "account.resetPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func declinePasswordReset() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1284770294)
return (FunctionDescription(name: "account.declinePasswordReset", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public struct langpack {
public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {

View File

@ -376,7 +376,7 @@ public func twoStepAuthData(_ network: Network) -> Signal<TwoStepAuthData, MTRpc
return network.request(Api.functions.account.getPassword())
|> map { config -> TwoStepAuthData in
switch config {
case let .password(flags, currentAlgo, srpB, srpId, hint, emailUnconfirmedPattern, newAlgo, newSecureAlgo, secureRandom):
case let .password(flags, currentAlgo, srpB, srpId, hint, emailUnconfirmedPattern, newAlgo, newSecureAlgo, secureRandom, pendingResetDate):
let hasRecovery = (flags & (1 << 0)) != 0
let hasSecureValues = (flags & (1 << 1)) != 0

View File

@ -370,10 +370,11 @@ public enum PasswordRecoveryError {
case invalidCode
case limitExceeded
case expired
case generic
}
public func performPasswordRecovery(accountManager: AccountManager, account: UnauthorizedAccount, code: String, syncContacts: Bool) -> Signal<Void, PasswordRecoveryError> {
return account.network.request(Api.functions.auth.recoverPassword(code: code))
public func checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
return network.request(Api.functions.auth.checkRecoveryPassword(code: code))
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
@ -383,26 +384,67 @@ public func performPasswordRecovery(accountManager: AccountManager, account: Una
return .invalidCode
}
}
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .authorization(_, _, user):
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
/*transaction.updatePeersInternal([user], update: { current, peer -> Peer? in
return peer
})*/
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> Void in
switchToAuthorizedAccount(transaction: transaction, account: account)
}
case .authorizationSignUpRequired:
return .complete()
|> mapToSignal { result -> Signal<Never, PasswordRecoveryError> in
return .complete()
}
}
public func performPasswordRecovery(accountManager: AccountManager, account: UnauthorizedAccount, code: String, syncContacts: Bool, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<Void, PasswordRecoveryError> {
return twoStepAuthData(account.network)
|> 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 {
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))
|> 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
}
}
|> switchToLatest
|> mapError { _ -> PasswordRecoveryError in }
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .authorization(_, _, user):
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> Void in
switchToAuthorizedAccount(transaction: transaction, account: account)
}
case .authorizationSignUpRequired:
return .complete()
}
}
|> switchToLatest
|> mapError { _ -> PasswordRecoveryError in }
}
}
}

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 130
return 131
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -335,21 +335,32 @@ public enum RecoverTwoStepVerificationPasswordError {
}
public func recoverTwoStepVerificationPassword(network: Network, code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> {
return network.request(Api.functions.auth.recoverPassword(code: code), 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
return 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()
}
|> mapToSignal { _ -> Signal<Void, RecoverTwoStepVerificationPasswordError> in
return .complete()
}
}
}
public func cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {

View File

@ -17,6 +17,7 @@ import SettingsUI
import PhoneNumberFormat
import LegacyComponents
import LegacyMediaPickerUI
import PasswordSetupUI
private enum InnerState: Equatable {
case state(UnauthorizedAccountStateContents)
@ -564,29 +565,68 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
}).start()
})
controller.recoverWithCode = { [weak self, weak controller] code in
if let strongSelf = self {
controller?.inProgress = true
strongSelf.actionDisposable.set((performPasswordRecovery(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, code: code, syncContacts: syncContacts) |> deliverOnMainQueue).start(error: { error in
Queue.mainQueue().async {
if let strongSelf = self, let controller = controller {
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
}
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))
}
}
}))
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, network: strongSelf.account.network, 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?.inProgress = true
strongSelf.actionDisposable.set((performPasswordRecovery(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, code: code, syncContacts: syncContacts, updatedPassword: .password(password: "123", hint: "", email: nil))
|> deliverOnMainQueue).start(error: { error in
Queue.mainQueue().async {
if let strongSelf = self, let controller = controller {
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
}
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))
}
}
}))*/
}
controller.noAccess = { [weak self, weak controller] in
if let strongSelf = self, let controller = controller {