Skip to content

Commit

Permalink
Improve error handling and showing for Coinzix login and verify codes
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed Jul 19, 2023
1 parent 62110db commit 2ee4cb8
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {
override func viewDidLoad() {
super.viewDidLoad()

title = "coinzix_verify_withdraw.title".localized
title = "coinzix_verify.title".localized

navigationItem.largeTitleDisplayMode = .never
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "button.cancel".localized, style: .plain, target: self, action: #selector(onTapCancel))
Expand All @@ -53,7 +53,8 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {
tableView.backgroundColor = .clear
tableView.separatorStyle = .none

emailPinInputCell.inputPlaceholder = "coinzix_verify_withdraw.email_pin".localized
emailPinInputCell.inputPlaceholder = "coinzix_verify.email_pin".localized
emailPinInputCell.keyboardType = .numberPad
emailPinInputCell.onChangeHeight = { [weak self] in self?.reloadHeights() }
emailPinInputCell.onChangeText = { [weak self] in self?.viewModel.onChange(emailPin: $0 ?? "") }
emailPinInputCell.onFetchText = { [weak self] in
Expand All @@ -62,7 +63,8 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {
}
emailPinInputCell.onResend = { [weak self] in self?.viewModel.onTapResend() }

googlePinInputCell.inputPlaceholder = "coinzix_verify_withdraw.google_pin".localized
googlePinInputCell.inputPlaceholder = "coinzix_verify.google_pin".localized
googlePinInputCell.keyboardType = .numberPad
googlePinInputCell.onChangeHeight = { [weak self] in self?.reloadHeights() }
googlePinInputCell.onChangeText = { [weak self] in self?.viewModel.onChange(googlePin: $0 ?? "") }
googlePinInputCell.onFetchText = { [weak self] in
Expand All @@ -87,13 +89,13 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {

stackView.addArrangedSubview(submitButton)
submitButton.set(style: .yellow)
submitButton.setTitle("coinzix_verify_withdraw.submit".localized, for: .normal)
submitButton.setTitle("coinzix_verify.submit".localized, for: .normal)
submitButton.addTarget(self, action: #selector(onTapSubmit), for: .touchUpInside)

stackView.addArrangedSubview(submittingButton)
submittingButton.set(style: .gray, accessoryType: .spinner)
submittingButton.isEnabled = false
submittingButton.setTitle("coinzix_verify_withdraw.submit".localized, for: .normal)
submittingButton.setTitle("coinzix_verify.submit".localized, for: .normal)

viewModel.$submitButtonState
.receive(on: DispatchQueue.main)
Expand All @@ -112,7 +114,7 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {

viewModel.errorPublisher
.receive(on: DispatchQueue.main)
.sink { text in HudHelper.instance.showErrorBanner(title: text) }
.sink { [weak self] in self?.show(error: $0) }
.store(in: &cancellables)

tableView.buildSections()
Expand Down Expand Up @@ -167,6 +169,21 @@ class CoinzixVerifyViewController: KeyboardAwareViewController {
viewModel.onTapSubmit()
}

private func show(error: String) {
let viewController = BottomSheetModule.viewController(
image: .local(image: UIImage(named: "warning_2_24")?.withTintColor(.themeLucian)),
title: "coinzix_verify.failed".localized,
items: [
.highlightedDescription(text: error, style: .red)
],
buttons: [
.init(style: .yellow, title: "button.ok".localized)
]
)

present(viewController, animated: true)
}

}

extension CoinzixVerifyViewController: SectionsDataSource {
Expand Down Expand Up @@ -196,7 +213,7 @@ extension CoinzixVerifyViewController: SectionsDataSource {
rows: [
tableView.descriptionRow(
id: "email-pin-description",
text: "coinzix_verify_withdraw.email_pin.description".localized,
text: "coinzix_verify.email_pin.description".localized,
font: .subhead2,
textColor: .themeGray,
ignoreBottomMargin: true
Expand Down Expand Up @@ -226,7 +243,7 @@ extension CoinzixVerifyViewController: SectionsDataSource {
rows: [
tableView.descriptionRow(
id: "google-pin-description",
text: "coinzix_verify_withdraw.google_pin.description".localized,
text: "coinzix_verify.google_pin.description".localized,
font: .subhead2,
textColor: .themeGray,
ignoreBottomMargin: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ extension CoinzixVerifyViewModel {
}

var errorPublisher: AnyPublisher<String, Never> {
service.errorPublisher
.map { _ in "coinzix_verify_withdraw.failed".localized }
.eraseToAnyPublisher()
service.errorPublisher.map { $0.smartDescription }.eraseToAnyPublisher()
}

var twoFactorTypes: [CoinzixCexProvider.TwoFactorType] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class RestoreCoinzixService {
@PostPublished private(set) var state: State = .notReady

private let verifySubject = PassthroughSubject<(CoinzixVerifyModule.Mode, [CoinzixCexProvider.TwoFactorType]), Never>()
private let errorSubject = PassthroughSubject<String, Never>()
private let errorSubject = PassthroughSubject<Error, Never>()

init(networkManager: NetworkManager) {
self.networkManager = networkManager
Expand All @@ -32,27 +32,15 @@ class RestoreCoinzixService {
state = username.trimmingCharacters(in: .whitespaces).isEmpty || password.trimmingCharacters(in: .whitespaces).isEmpty ? .notReady : .ready
}

private func handle(loginResult: CoinzixCexProvider.LoginResult) {
switch loginResult {
case .success(let token, let secret, let twoFactorType):
let type: CoinzixCexProvider.TwoFactorType
private func handle(loginData: CoinzixCexProvider.LoginData) {
let type: CoinzixCexProvider.TwoFactorType

switch twoFactorType {
case .email: type = .email
case .authenticator: type = .authenticator
}

verifySubject.send((.login(token: token, secret: secret), [type]))
case .failed(let reason):
switch reason {
case .invalidCredentials(let attemptsLeft):
errorSubject.send("Invalid login credentials. Attempts left: \(attemptsLeft).")
case .tooManyAttempts(let unlockDate):
errorSubject.send("Too many invalid login attempts were made. Login is locked until \(DateHelper.instance.formatFullTime(from: unlockDate)).")
case .unknown(let message):
errorSubject.send(message)
}
switch loginData.twoFactorType {
case .email: type = .email
case .authenticator: type = .authenticator
}

verifySubject.send((.login(token: loginData.token, secret: loginData.secret), [type]))
}

}
Expand All @@ -63,7 +51,7 @@ extension RestoreCoinzixService {
verifySubject.eraseToAnyPublisher()
}

var errorPublisher: AnyPublisher<String, Never> {
var errorPublisher: AnyPublisher<Error, Never> {
errorSubject.eraseToAnyPublisher()
}

Expand All @@ -72,10 +60,10 @@ extension RestoreCoinzixService {

Task { [weak self, username, password, networkManager] in
do {
let loginResult = try await CoinzixCexProvider.login(username: username, password: password, networkManager: networkManager)
self?.handle(loginResult: loginResult)
let loginData = try await CoinzixCexProvider.login(username: username, password: password, networkManager: networkManager)
self?.handle(loginData: loginData)
} catch {
self?.errorSubject.send(error.smartDescription)
self?.errorSubject.send(error)
}

self?.state = .ready
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class RestoreCoinzixViewModel {
extension RestoreCoinzixViewModel {

var errorPublisher: AnyPublisher<String, Never> {
service.errorPublisher
service.errorPublisher.map { $0.smartDescription }.eraseToAnyPublisher()
}

var verifyPublisher: AnyPublisher<(CoinzixVerifyModule.Mode, [CoinzixCexProvider.TwoFactorType]), Never> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ extension CoinzixCexProvider {

extension CoinzixCexProvider {

static func login(username: String, password: String, networkManager: NetworkManager) async throws -> LoginResult {
static func login(username: String, password: String, networkManager: NetworkManager) async throws -> LoginData {
let parameters: Parameters = [
"username": username,
"password": password,
Expand All @@ -338,19 +338,19 @@ extension CoinzixCexProvider {

if response.status {
if let token = response.token, let secret = response.secret, let twoFactorTypeRaw = response.twoFactorTypeRaw, let twoFactorType = TwoFactorType(rawValue: twoFactorTypeRaw) {
return .success(token: token, secret: secret, twoFactorType: twoFactorType)
return LoginData(token: token, secret: secret, twoFactorType: twoFactorType)
}
} else {
if let leftAttempts = response.leftAttempts {
return .failed(reason: .invalidCredentials(attemptsLeft: leftAttempts))
throw LoginError.invalidCredentials(attemptsLeft: leftAttempts)
}

if let timeExpire = response.timeExpire {
return .failed(reason: .tooManyAttempts(unlockDate: Date(timeIntervalSince1970: TimeInterval(timeExpire))))
throw LoginError.tooManyAttempts(unlockDate: Date(timeIntervalSince1970: TimeInterval(timeExpire)))
}
}

return .failed(reason: .unknown(message: response.errors.map { $0.joined(separator: "\n") } ?? "Unknown error"))
throw LoginError.unknown(message: response.errors.map { $0.joined(separator: "\n") } ?? "Unknown error")
}

static func validateCode(code: String, token: String, networkManager: NetworkManager) async throws {
Expand All @@ -367,6 +367,10 @@ extension CoinzixCexProvider {
)

guard response.status else {
if let errors = response.errors {
throw VerifyError(messages: errors)
}

throw RequestError.negativeStatus
}
}
Expand All @@ -375,15 +379,32 @@ extension CoinzixCexProvider {

extension CoinzixCexProvider {

enum LoginResult {
case success(token: String, secret: String, twoFactorType: TwoFactorType)
case failed(reason: LoginFailureReason)
struct LoginData {
let token: String
let secret: String
let twoFactorType: TwoFactorType
}

enum LoginFailureReason {
enum LoginError: LocalizedError {
case invalidCredentials(attemptsLeft: Int)
case tooManyAttempts(unlockDate: Date)
case unknown(message: String)

var errorDescription: String? {
switch self {
case .invalidCredentials(let attemptsLeft): return "Invalid login credentials. Attempts left: \(attemptsLeft)."
case .tooManyAttempts(let unlockDate): return "Too many invalid login attempts were made. Login is locked until \(DateHelper.instance.formatFullTime(from: unlockDate))."
case .unknown(let message): return message
}
}
}

struct VerifyError: LocalizedError {
let messages: [String]

var errorDescription: String? {
messages.joined(separator: "\n")
}
}

enum TwoFactorType: Int {
Expand Down Expand Up @@ -531,9 +552,11 @@ extension CoinzixCexProvider {

private struct StatusResponse: ImmutableMappable {
let status: Bool
let errors: [String]?

init(map: Map) throws {
status = try map.value("status")
errors = try? map.value("errors")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ extension PasteInputCell {
set { pasteInputView.inputPlaceholder = newValue }
}

var keyboardType: UIKeyboardType {
get { pasteInputView.keyboardType }
set { pasteInputView.keyboardType = newValue }
}

var inputText: String? {
get { pasteInputView.inputText }
set { pasteInputView.inputText = newValue }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import ThemeKit
import SnapKit

class ResendPasteInputCell: UITableViewCell {
private let view: ResendPasteInputView
private let view = ResendPasteInputView()

init() {
self.view = ResendPasteInputView()
super.init(style: .default, reuseIdentifier: nil)

backgroundColor = .clear
Expand All @@ -31,6 +30,11 @@ extension ResendPasteInputCell {
set { view.inputPlaceholder = newValue }
}

var keyboardType: UIKeyboardType {
get { view.keyboardType }
set { view.keyboardType = newValue }
}

var inputText: String? {
get { view.inputText }
set { view.inputText = newValue }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ extension PasteInputView {
set { inputStackView.placeholder = newValue }
}

var keyboardType: UIKeyboardType {
get { inputStackView.keyboardType }
set { inputStackView.keyboardType = newValue }
}

var inputText: String? {
get { inputStackView.text }
set {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ extension ResendPasteInputView {
set { inputStackView.placeholder = newValue }
}

var keyboardType: UIKeyboardType {
get { inputStackView.keyboardType }
set { inputStackView.keyboardType = newValue }
}

var inputText: String? {
get { inputStackView.text }
set {
Expand Down
19 changes: 7 additions & 12 deletions UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1658,15 +1658,10 @@ Go to Settings - > Unstoppable and allow access to the camera.";

// Coinzix Verify Withdraw

"coinzix_verify_withdraw.title" = "Security Verification";
"coinzix_verify_withdraw.email.title" = "Email Verification Code";
"coinzix_verify_withdraw.email.description" = "Enter verification code sent to %@";
"coinzix_verify_withdraw.google.title" = "Google Authentication Code";
"coinzix_verify_withdraw.google.description" = "Enter Google authentication code from your Google Authenticator App";
"coinzix_verify_withdraw.get_code" = "Get Code";
"coinzix_verify_withdraw.failed" = "Failed to verify";
"coinzix_verify_withdraw.submit" = "Submit";
"coinzix_verify_withdraw.email_pin" = "Verification Code";
"coinzix_verify_withdraw.email_pin.description" = "Enter verification code sent to [email protected]";
"coinzix_verify_withdraw.google_pin" = "Google Authentication Code";
"coinzix_verify_withdraw.google_pin.description" = "Enter google authentication code from your Google Authenticator App";
"coinzix_verify.title" = "Security Verification";
"coinzix_verify.failed" = "Failed to verify";
"coinzix_verify.submit" = "Submit";
"coinzix_verify.email_pin" = "Verification Code";
"coinzix_verify.email_pin.description" = "Enter verification code sent to your email";
"coinzix_verify.google_pin" = "Google Authentication Code";
"coinzix_verify.google_pin.description" = "Enter google authentication code from your Google Authenticator App";

0 comments on commit 2ee4cb8

Please sign in to comment.