diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 8a4d8e6c88..04c5a591e2 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -952,7 +952,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz switch value { case .notSet: break - case let .set(hint, _, _, _): + case let .set(hint, _, _, _, _): if !hint.isEmpty { strongSelf.passwordTip = hint } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift index 71419adcbb..5e125dfa23 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift @@ -26,8 +26,9 @@ private final class TwoStepVerificationUnlockSettingsControllerArguments { let openResetPendingEmail: () -> Void let updateEmailCode: (String) -> Void let openConfirmEmail: () -> Void + let declinePasswordReset: () -> Void - init(updatePasswordText: @escaping (String) -> Void, checkPassword: @escaping () -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void, updateEmailCode: @escaping (String) -> Void, openConfirmEmail: @escaping () -> Void) { + init(updatePasswordText: @escaping (String) -> Void, checkPassword: @escaping () -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void, updateEmailCode: @escaping (String) -> Void, openConfirmEmail: @escaping () -> Void, declinePasswordReset: @escaping () -> Void) { self.updatePasswordText = updatePasswordText self.checkPassword = checkPassword self.openForgotPassword = openForgotPassword @@ -37,6 +38,7 @@ private final class TwoStepVerificationUnlockSettingsControllerArguments { self.openResetPendingEmail = openResetPendingEmail self.updateEmailCode = updateEmailCode self.openConfirmEmail = openConfirmEmail + self.declinePasswordReset = declinePasswordReset } } @@ -132,47 +134,51 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { }, action: { arguments.checkPassword() }) - case let .passwordEntryInfo(theme, text): + case let .passwordEntryInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in switch action { - case .tap: - arguments.openForgotPassword() + case let .tap(item): + if item == "forgot" { + arguments.openForgotPassword() + } else if item == "declineReset" { + arguments.declinePasswordReset() + } } }) - case let .passwordSetup(theme, text): + case let .passwordSetup(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupPassword() }) - case let .passwordSetupInfo(theme, text): + case let .passwordSetupInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) - case let .changePassword(theme, text): + case let .changePassword(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupPassword() }) - case let .turnPasswordOff(theme, text): + case let .turnPasswordOff(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openDisablePassword() }) - case let .setupRecoveryEmail(theme, text): + case let .setupRecoveryEmail(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupEmail() }) - case let .passwordInfo(theme, text): + case let .passwordInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .pendingEmailConfirmInfo(theme, text): + case let .pendingEmailConfirmInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .pendingEmailConfirmCode(theme, strings, title, text): + case let .pendingEmailConfirmCode(_, _, title, text): return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in arguments.updateEmailCode(value) }, action: {}) - case let .pendingEmailInfo(theme, text): + case let .pendingEmailInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in switch action { case .tap: arguments.openResetPendingEmail() } }) - case let .pendingEmailOpenConfirm(theme, text): + case let .pendingEmailOpenConfirm(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openConfirmEmail() }) @@ -204,13 +210,27 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData entries.append(.passwordSetup(presentationData.theme, presentationData.strings.TwoStepAuth_SetPassword)) entries.append(.passwordSetupInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetPasswordHelp)) } - case let .set(hint, _, _): + case let .set(hint, _, _, pendingResetTimestamp): entries.append(.passwordEntry(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_EnterPasswordPassword, state.passwordText)) - if hint.isEmpty { - entries.append(.passwordEntryInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)")) + var text: String = presentationData.strings.TwoStepAuth_EnterPasswordHint(escapedPlaintextForMarkdown(hint)).0 + "\n\n" + presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)" + if !hint.isEmpty { + text = presentationData.strings.TwoStepAuth_EnterPasswordHint(escapedPlaintextForMarkdown(hint)).0 + "\n\n" + presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)" } else { - entries.append(.passwordEntryInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EnterPasswordHint(escapedPlaintextForMarkdown(hint)).0 + "\n\n" + presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)")) + text = presentationData.strings.TwoStepAuth_EnterPasswordHelp + "\n\n[" + presentationData.strings.TwoStepAuth_EnterPasswordForgot + "](forgot)" } + + if let pendingResetTimestamp = pendingResetTimestamp { + let remainingSeconds = pendingResetTimestamp - Int32(Date().timeIntervalSince1970) + if remainingSeconds <= 0 { + text.append("\nPassword reset is available") + } else { + text.append("\nPassword reset available in \(remainingSeconds) s") + } + + text.append("\n[Cancel reset](declineReset)") + } + + entries.append(.passwordEntryInfo(presentationData.theme, text)) } } case let .manage(_, emailSet, pendingEmail, _): @@ -240,14 +260,14 @@ struct TwoStepVerificationPendingEmailState: Equatable { enum TwoStepVerificationAccessConfiguration: Equatable { case notSet(pendingEmail: TwoStepVerificationPendingEmailState?) - case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool) + case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool, pendingResetTimestamp: Int32?) init(configuration: TwoStepVerificationConfiguration, password: String?) { switch configuration { case let .notSet(pendingEmail): self = .notSet(pendingEmail: pendingEmail.flatMap({ TwoStepVerificationPendingEmailState(password: password, email: $0) })) - case let .set(hint, hasRecoveryEmail, _, hasSecureValues): - self = .set(hint: hint, hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues) + case let .set(hint, hasRecoveryEmail, _, hasSecureValues, pendingResetTimestamp): + self = .set(hint: hint, hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues, pendingResetTimestamp: pendingResetTimestamp) } } } @@ -420,7 +440,6 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: |> mapToSignal { settings -> Signal<(TwoStepVerificationSettings, TwoStepVerificationPendingEmail?), AuthorizationPasswordVerificationError> in return twoStepVerificationConfiguration(account: context.account) |> mapError { _ -> AuthorizationPasswordVerificationError in - return .generic } |> map { configuration in var pendingEmail: TwoStepVerificationPendingEmail? @@ -469,7 +488,7 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: if let configuration = configuration { let presentationData = context.sharedContext.currentPresentationData.with { $0 } switch configuration { - case let .set(_, hasRecoveryEmail, _): + case let .set(_, hasRecoveryEmail, _, pendingResetTimestamp): if hasRecoveryEmail { updateState { state in var state = state @@ -506,7 +525,23 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) })) } else { - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + if true || pendingResetTimestamp == nil { + let _ = (requestTwoStepPasswordReset(network: context.account.network) + |> deliverOnMainQueue).start(next: { result in + switch result { + case .done, .waitingForReset: + dataPromise.set(twoStepVerificationConfiguration(account: context.account) + |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: nil)) + }) + case .declined: + break + case let .error(reason): + break + } + }) + } else { + presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } } case .notSet: break @@ -762,6 +797,13 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } }) + }, declinePasswordReset: { + let _ = (declineTwoStepPasswordReset(network: context.account.network) + |> deliverOnMainQueue).start(completed: { + dataPromise.set(twoStepVerificationConfiguration(account: context.account) + |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: nil)) + }) + }) }) var initialFocusImpl: (() -> Void)? diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index 1dd1a30329..808361feb3 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -370,6 +370,7 @@ public struct TwoStepAuthData { public let unconfirmedEmailPattern: String? public let secretRandom: Data public let nextSecurePasswordDerivation: TwoStepSecurePasswordDerivation + public let pendingResetTimestamp: Int32? } public func twoStepAuthData(_ network: Network) -> Signal { @@ -398,7 +399,7 @@ public func twoStepAuthData(_ network: Network) -> Signal Signal { - return network.request(Api.functions.auth.checkRecoveryPassword(code: code)) + return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false) |> mapError { error -> PasswordRecoveryError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -416,7 +416,7 @@ public func performPasswordRecovery(accountManager: AccountManager, account: Una if newSettings != nil { flags |= 1 << 0 } - return account.network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings)) + 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 diff --git a/submodules/TelegramCore/Sources/TwoStepVerification.swift b/submodules/TelegramCore/Sources/TwoStepVerification.swift index 195b34bdb0..08bcc49353 100644 --- a/submodules/TelegramCore/Sources/TwoStepVerification.swift +++ b/submodules/TelegramCore/Sources/TwoStepVerification.swift @@ -8,7 +8,7 @@ import SyncCore public enum TwoStepVerificationConfiguration { case notSet(pendingEmail: TwoStepVerificationPendingEmail?) - case set(hint: String, hasRecoveryEmail: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool) + case set(hint: String, hasRecoveryEmail: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool, pendingResetTimestamp: Int32?) } public func twoStepVerificationConfiguration(account: Account) -> Signal { @@ -18,7 +18,7 @@ public func twoStepVerificationConfiguration(account: Account) -> Signal Signal { + return network.request(Api.functions.account.resetPassword(), automaticFloodWait: false) + |> map { _ -> RequestTwoStepPasswordResetResult in + return .done + } + |> `catch` { error -> Signal in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .single(.error(reason: .limitExceeded)) + } else if error.errorDescription.hasPrefix("RESET_WAIT_") { + if let remainingSeconds = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "RESET_WAIT_".count)...]) { + let timestamp = Int32(network.globalTime) + return .single(.waitingForReset(resetAtTimestamp: timestamp + remainingSeconds)) + } else { + return .single(.error(reason: .generic)) + } + } else if error.errorDescription.hasPrefix("RESET_PREVIOUS_WAIT_") { + if let remainingSeconds = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "RESET_PREVIOUS_WAIT_".count)...]) { + let timestamp = Int32(network.globalTime) + return .single(.waitingForReset(resetAtTimestamp: timestamp + remainingSeconds)) + } else { + return .single(.error(reason: .generic)) + } + } else if error.errorDescription == "RESET_PREVIOUS_DECLINE" { + return .single(.declined) + } else { + return .single(.error(reason: .generic)) + } + } +} + +public func declineTwoStepPasswordReset(network: Network) -> Signal { + return network.request(Api.functions.account.declinePasswordReset()) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues +} diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift index 306b3b257d..30c410b7dd 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift @@ -604,29 +604,6 @@ public final class AuthorizationSequenceController: NavigationController, MFMail }) 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 {