mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Password reset fixes
This commit is contained in:
parent
ad5d4f5c75
commit
a2f0f5b1ce
@ -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";
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user