Skip to content

Commit d82d43d

Browse files
committed
Added recovery code feature.
1 parent db548b4 commit d82d43d

File tree

8 files changed

+400
-21
lines changed

8 files changed

+400
-21
lines changed

iOS-Email-Client.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
/* Begin PBXBuildFile section */
1010
347A659922E126F1002D9151 /* LinkFileHeaderData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347A659822E126F1002D9151 /* LinkFileHeaderData.swift */; };
1111
34857664235918BC002E2049 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5FD825205C857C000E5F14 /* Utils.swift */; };
12+
348DD9DA2361FF1C000DC704 /* GenericSingleInputPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348DD9D92361FF1C000DC704 /* GenericSingleInputPopover.swift */; };
13+
348DD9DD23620C59000DC704 /* GenericSingleInputPopover.xib in Resources */ = {isa = PBXBuildFile; fileRef = 348DD9DB23620C59000DC704 /* GenericSingleInputPopover.xib */; };
1214
3498992A22EA17C900FB00DC /* RemoveDevicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3498992922EA17C900FB00DC /* RemoveDevicesViewController.swift */; };
1315
34C4173622BC20530095DF23 /* FirebaseCoreDiagnostics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C4172F22BC20520095DF23 /* FirebaseCoreDiagnostics.framework */; };
1416
34C4173722BC20530095DF23 /* FirebaseInstanceID.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C4173022BC20520095DF23 /* FirebaseInstanceID.framework */; };
@@ -527,6 +529,8 @@
527529

528530
/* Begin PBXFileReference section */
529531
347A659822E126F1002D9151 /* LinkFileHeaderData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkFileHeaderData.swift; sourceTree = "<group>"; };
532+
348DD9D92361FF1C000DC704 /* GenericSingleInputPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericSingleInputPopover.swift; sourceTree = "<group>"; };
533+
348DD9DC23620C59000DC704 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "iOS-Email-Client/Views/Base.lproj/GenericSingleInputPopover.xib"; sourceTree = "<group>"; };
530534
3498992922EA17C900FB00DC /* RemoveDevicesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveDevicesViewController.swift; sourceTree = "<group>"; };
531535
34C4172F22BC20520095DF23 /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FirebaseCoreDiagnostics.framework; sourceTree = "<group>"; };
532536
34C4173022BC20520095DF23 /* FirebaseInstanceID.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FirebaseInstanceID.framework; sourceTree = "<group>"; };
@@ -1144,6 +1148,7 @@
11441148
7CFF2EE720766532002B19DB /* MenuViewController.swift */,
11451149
A70609FE21A9C0110066BFA4 /* OptionsPickerUIPopover.swift */,
11461150
7C4D33C6212C7B8400045B09 /* PasswordUIPopover.swift */,
1151+
348DD9D92361FF1C000DC704 /* GenericSingleInputPopover.swift */,
11471152
D643394F22013C4C00B74C1D /* ProfileEditorViewController.swift */,
11481153
7C4D33BC212B4F9D00045B09 /* RecoveryEmailViewController.swift */,
11491154
7CCD73E5212DF6F5002C8C7F /* RemoveDeviceUIPopover.swift */,
@@ -1365,6 +1370,7 @@
13651370
7C4D33C0212C733700045B09 /* GenericAlertUIPopover.xib */,
13661371
7C6DB465215173A6001B3749 /* GenericDualAnswerUIPopover.swift */,
13671372
7C6DB467215173D0001B3749 /* GenericDualAnswerUIPopover.xib */,
1373+
348DD9DB23620C59000DC704 /* GenericSingleInputPopover.xib */,
13681374
7CBD20C42025141F00D70B87 /* GradientUIView.swift */,
13691375
7C1E82C32116BDF2007CA853 /* HintUIView.swift */,
13701376
7C1E82C12116BD43007CA853 /* HintUIView.xib */,
@@ -1839,6 +1845,7 @@
18391845
A76D303622565C64004F8747 /* SettingsOptionTableCell.xib in Resources */,
18401846
41AAC54F201AA05E00809C4D /* mark_read.png in Resources */,
18411847
A7FFCF30217A910A00FFFDFF /* LabelsFooterTableViewCell.xib in Resources */,
1848+
348DD9DD23620C59000DC704 /* GenericSingleInputPopover.xib in Resources */,
18421849
412CB0422037402D005D1B3F /* NunitoSans-SemiBold.ttf in Resources */,
18431850
A7FFCF1E217A90B300FFFDFF /* ContactsDetailUIPopover.xib in Resources */,
18441851
7C63AB56205330A00053219F /* AccountFromCell.xib in Resources */,
@@ -2016,6 +2023,7 @@
20162023
950AFF3021FA6A7E003115E7 /* EmailSourceViewController.swift in Sources */,
20172024
A70E39D72239BBF0005D610B /* AccountTableCell.swift in Sources */,
20182025
7CCA060D210B687200BC4CEF /* Dummy.swift in Sources */,
2026+
348DD9DA2361FF1C000DC704 /* GenericSingleInputPopover.swift in Sources */,
20192027
7CAC1CEE20F6AE7B004A50C6 /* PaddingUILabel.swift in Sources */,
20202028
7CDAD26A2040B80A0080A32C /* CriptextPreKeyStore.swift in Sources */,
20212029
7CFE8D5020BC688900998A2F /* KeyboardManager.swift in Sources */,
@@ -2357,6 +2365,14 @@
23572365
/* End PBXTargetDependency section */
23582366

23592367
/* Begin PBXVariantGroup section */
2368+
348DD9DB23620C59000DC704 /* GenericSingleInputPopover.xib */ = {
2369+
isa = PBXVariantGroup;
2370+
children = (
2371+
348DD9DC23620C59000DC704 /* Base */,
2372+
);
2373+
name = GenericSingleInputPopover.xib;
2374+
sourceTree = "<group>";
2375+
};
23602376
41B6AAB32017987500D428B7 /* Main.storyboard */ = {
23612377
isa = PBXVariantGroup;
23622378
children = (

iOS-Email-Client/Base.lproj/Login.storyboard

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@
735735
</constraints>
736736
</imageView>
737737
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign In" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AGe-4s-CNR">
738-
<rect key="frame" x="33" y="115" width="309" height="37"/>
738+
<rect key="frame" x="33" y="117.5" width="309" height="31.5"/>
739739
<fontDescription key="fontDescription" name="NunitoSans-SemiBold" family="Nunito Sans" pointSize="27"/>
740740
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
741741
<nil key="highlightedColor"/>
@@ -755,7 +755,7 @@ was rejected by a verified device</string>
755755
<nil key="highlightedColor"/>
756756
</label>
757757
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="device-alert" translatesAutoresizingMaskIntoConstraints="NO" id="u04-Cm-YDi">
758-
<rect key="frame" x="162.5" y="307" width="50.000000000000014" height="50"/>
758+
<rect key="frame" x="162.5" y="307" width="50" height="50"/>
759759
<constraints>
760760
<constraint firstAttribute="width" constant="50" id="LZr-Cj-ZWh"/>
761761
<constraint firstAttribute="height" constant="50" id="a7m-S3-MgJ"/>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//
2+
// GenericSingleInputPopover.swift
3+
// iOS-Email-Client
4+
//
5+
// Created by Jorge Blacio on 10/24/19.
6+
// Copyright © 2019 Criptext Inc. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Material
11+
12+
class GenericSingleInputPopover: BaseUIPopover {
13+
14+
var answerShouldDismiss = true
15+
var canDismiss = true
16+
var onOkPress: ((String) -> (Void))?
17+
var onCancelPress: (() -> (Void))?
18+
var initialTitle: String?
19+
var titleTextColor: UIColor?
20+
var rightOption: String?
21+
var leftOption: String?
22+
var buttonTextColor: UIColor?
23+
var initialAttrMessage: NSAttributedString?
24+
var initialMessage: String?
25+
var keyboardType: UIKeyboardType = UIKeyboardType.default
26+
@IBOutlet weak var titleHeightConstraint: NSLayoutConstraint!
27+
@IBOutlet weak var messageHeightConstraint: NSLayoutConstraint!
28+
@IBOutlet weak var okButton: UIButton!
29+
@IBOutlet weak var inputTextField: TextField!
30+
@IBOutlet weak var cancelButton: UIButton!
31+
@IBOutlet weak var loader: UIActivityIndicatorView!
32+
@IBOutlet weak var titleLabel: UILabel!
33+
@IBOutlet weak var messageLabel: UILabel!
34+
35+
init(){
36+
super.init("GenericSingleInputPopover")
37+
}
38+
39+
required init?(coder aDecoder: NSCoder) {
40+
super.init(coder: aDecoder)
41+
}
42+
43+
override func viewDidLoad() {
44+
super.viewDidLoad()
45+
shouldDismiss = canDismiss
46+
let _ = inputTextField.becomeFirstResponder()
47+
inputTextField.keyboardType = keyboardType
48+
if let title = initialTitle {
49+
titleLabel.text = title
50+
}
51+
if let attrMessage = initialAttrMessage {
52+
messageLabel.attributedText = attrMessage
53+
let height = UIUtils.getLabelHeight(attrMessage.string, width: messageLabel.frame.width, fontSize: 14)
54+
messageHeightConstraint.constant = height
55+
if let title = initialTitle {
56+
titleHeightConstraint.constant = UIUtils.getLabelHeight(title, width: messageLabel.frame.width, fontSize: 16) + 30
57+
}
58+
} else if let message = initialMessage {
59+
messageLabel.text = message
60+
} else {
61+
messageHeightConstraint.constant = 0
62+
}
63+
64+
if let bText = rightOption {
65+
okButton.setTitle(bText, for: .normal)
66+
}
67+
68+
showLoader(false)
69+
applyTheme()
70+
}
71+
72+
func applyTheme() {
73+
let theme: Theme = ThemeManager.shared.theme
74+
navigationController?.navigationBar.barTintColor = theme.toolbar
75+
view.backgroundColor = theme.background
76+
titleLabel.textColor = titleTextColor ?? theme.mainText
77+
messageLabel.textColor = theme.mainText
78+
inputTextField.detailColor = theme.alert
79+
inputTextField.textColor = theme.mainText
80+
inputTextField.placeholderLabel.textColor = theme.mainText
81+
inputTextField.attributedPlaceholder = NSAttributedString(string: String.localize("RECOVERY_CODE_DIALOG_TITLE"), attributes: [NSAttributedString.Key.foregroundColor: theme.placeholder])
82+
okButton.backgroundColor = theme.popoverButton
83+
cancelButton.backgroundColor = theme.popoverButton
84+
okButton.setTitleColor(buttonTextColor ?? theme.mainText, for: .normal)
85+
cancelButton.setTitleColor(theme.mainText, for: .normal)
86+
loader.color = theme.loader
87+
}
88+
89+
90+
@IBAction func okPress(_ sender: Any) {
91+
guard let password = inputTextField.text else {
92+
return
93+
}
94+
self.showLoader(true)
95+
inputTextField.resignFirstResponder()
96+
guard answerShouldDismiss else {
97+
self.onOkPress?(password)
98+
return
99+
}
100+
self.dismiss(animated: true, completion: { [weak self] in
101+
self?.onOkPress?(password)
102+
})
103+
}
104+
105+
func showLoader(_ show: Bool){
106+
self.okButton.isEnabled = !show
107+
self.cancelButton.isEnabled = !show
108+
self.inputTextField.isEnabled = !show
109+
self.loader.isHidden = !show
110+
guard show else {
111+
loader.stopAnimating()
112+
return
113+
}
114+
loader.startAnimating()
115+
}
116+
117+
@IBAction func cancelPress(_ sender: Any) {
118+
self.dismiss(animated: true, completion: { [weak self] in
119+
self?.onCancelPress?()
120+
})
121+
}
122+
}

iOS-Email-Client/Controllers/Login/LoginDeviceViewController.swift

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class LoginDeviceViewController: UIViewController{
3939
self.sendLinkAuthRequest()
4040
return
4141
}
42-
signWithPasswordButton.isHidden = true
42+
signWithPasswordButton.setTitle(String.localize("SEND_RECOVERY_CODE"), for: .normal)
4343
self.scheduleWorker.start()
4444
}
4545

@@ -71,26 +71,56 @@ class LoginDeviceViewController: UIViewController{
7171
}
7272

7373
func presentLoginPasswordPopover() {
74-
let popover = GenericDualAnswerUIPopover()
75-
popover.leftOption = String.localize("CANCEL")
76-
popover.rightOption = String.localize("YES")
77-
popover.initialTitle = String.localize("WARNING")
78-
let regularAttrs = [NSAttributedString.Key.font: Font.regular.size(14)!]
79-
let boldAttrs = [NSAttributedString.Key.font: Font.bold.size(14)!]
80-
let attrText = NSMutableAttributedString(string: String.localize("IF_YOU_SIGN_IN"), attributes: regularAttrs)
81-
let attrBold = NSAttributedString(string: String.localize("MAILBOX_HISTORY"), attributes: boldAttrs)
82-
let attrText2 = NSMutableAttributedString(string: String.localize("ON_THIS_DEVICE"), attributes: regularAttrs)
83-
attrText.append(attrBold)
84-
attrText.append(attrText2)
85-
popover.attributedMessage = attrText
86-
popover.onResponse = { [weak self] ok in
87-
guard ok,
88-
let weakSelf = self else {
89-
return
74+
if(loginData.isTwoFactor){
75+
APIManager.generateRecoveryCode(recipientId: loginData.username, domain: loginData.domain, token: loginData.jwt!){ (responseData) in
76+
switch(responseData){
77+
case .Success, .BadRequest:
78+
self.scheduleWorker.cancel()
79+
var popover: GenericSingleInputPopover? = nil
80+
popover = GenericSingleInputPopover()
81+
popover?.answerShouldDismiss = false
82+
popover?.canDismiss = false
83+
popover?.leftOption = String.localize("CANCEL")
84+
popover?.rightOption = String.localize("SEND")
85+
popover?.initialTitle = String.localize("RECOVERY_CODE_DIALOG_TITLE")
86+
popover?.initialMessage = String.localize("RECOVERY_CODE_DIALOG_MESSAGE")
87+
popover?.keyboardType = UIKeyboardType.decimalPad
88+
popover?.onOkPress = { [weak self] text in
89+
guard let weakSelf = self else {
90+
return
91+
}
92+
weakSelf.sendRecoveryCode(popover: popover, code: text)
93+
}
94+
popover?.onCancelPress = {
95+
popover = nil
96+
}
97+
self.presentPopover(popover: popover!, height: 245)
98+
default:
99+
return
100+
}
101+
}
102+
} else {
103+
let popover = GenericDualAnswerUIPopover()
104+
popover.leftOption = String.localize("CANCEL")
105+
popover.rightOption = String.localize("YES")
106+
popover.initialTitle = String.localize("WARNING")
107+
let regularAttrs = [NSAttributedString.Key.font: Font.regular.size(14)!]
108+
let boldAttrs = [NSAttributedString.Key.font: Font.bold.size(14)!]
109+
let attrText = NSMutableAttributedString(string: String.localize("IF_YOU_SIGN_IN"), attributes: regularAttrs)
110+
let attrBold = NSAttributedString(string: String.localize("MAILBOX_HISTORY"), attributes: boldAttrs)
111+
let attrText2 = NSMutableAttributedString(string: String.localize("ON_THIS_DEVICE"), attributes: regularAttrs)
112+
attrText.append(attrBold)
113+
attrText.append(attrText2)
114+
popover.attributedMessage = attrText
115+
popover.onResponse = { [weak self] ok in
116+
guard ok,
117+
let weakSelf = self else {
118+
return
119+
}
120+
weakSelf.jumpToResetDevice()
90121
}
91-
weakSelf.jumpToResetDevice()
122+
presentPopover(popover: popover, height: 205)
92123
}
93-
presentPopover(popover: popover, height: 205)
94124
}
95125

96126
func jumpToResetDevice(){
@@ -101,6 +131,45 @@ class LoginDeviceViewController: UIViewController{
101131
navigationController?.pushViewController(controller, animated: true)
102132
}
103133

134+
func sendRecoveryCode(popover: GenericSingleInputPopover?, code: String){
135+
if(code == ""){
136+
popover?.showLoader(false)
137+
popover?.inputTextField.detail = String.localize("RECOVERY_CODE_VALIDATION_ERROR_EMPTY")
138+
} else if (code.count < 6){
139+
popover?.showLoader(false)
140+
popover?.inputTextField.detail = String.localize("RECOVERY_CODE_VALIDATION_ERROR")
141+
} else {
142+
guard let jwt = loginData.jwt else {
143+
self.navigationController?.popViewController(animated: true)
144+
return
145+
}
146+
APIManager.validateRecoveryCode(recipientId: loginData.username, domain: loginData.domain, code: code, token: jwt) { (responseData) in
147+
guard case let .SuccessDictionary(data) = responseData else {
148+
popover?.inputTextField.detail = String.localize("RECOVERY_CODE_DIALOG_ERROR")
149+
popover?.showLoader(false)
150+
return
151+
}
152+
let name = data["name"] as! String
153+
let deviceId = data["deviceId"] as! Int
154+
let signupData = SignUpData(username: self.loginData.username, password: self.loginData.password!, domain: self.loginData.domain, fullname: name, optionalEmail: nil)
155+
signupData.deviceId = deviceId
156+
signupData.token = jwt
157+
signupData.comingFromLogin = true
158+
popover?.showLoader(false)
159+
popover?.dismiss(animated: true, completion: nil)
160+
self.jumpToCreatingAccount(signupData: signupData)
161+
}
162+
}
163+
}
164+
165+
func jumpToCreatingAccount(signupData: SignUpData){
166+
let storyboard = UIStoryboard(name: "Login", bundle: nil)
167+
let controller = storyboard.instantiateViewController(withIdentifier: "creatingaccountview") as! CreatingAccountViewController
168+
controller.signupData = signupData
169+
controller.multipleAccount = self.multipleAccount
170+
self.present(controller, animated: true, completion: nil)
171+
}
172+
104173
func onFailure(){
105174
failureDeviceView.isHidden = false
106175
waitingDeviceView.isHidden = true

iOS-Email-Client/Managers/APIManager.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,4 +983,33 @@ class APIManager: SharedAPI {
983983
completion(responseData)
984984
}
985985
}
986+
987+
class func generateRecoveryCode(recipientId: String, domain: String,token: String, completion: @escaping ((ResponseData) -> Void)) {
988+
let url = "\(self.baseUrl)/user/2fa/generatecode"
989+
let headers = ["Authorization": "Bearer \(token)",
990+
versionHeader: apiVersion]
991+
let params = [
992+
"recipientId": recipientId,
993+
"domain": domain
994+
] as [String: Any]
995+
Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseString { (response) in
996+
let responseData = handleResponse(response, satisfy: .success)
997+
completion(responseData)
998+
}
999+
}
1000+
1001+
class func validateRecoveryCode(recipientId: String, domain: String, code: String, token: String, completion: @escaping ((ResponseData) -> Void)) {
1002+
let url = "\(self.baseUrl)/user/2fa/validatecode"
1003+
let headers = ["Authorization": "Bearer \(token)",
1004+
versionHeader: apiVersion]
1005+
let params = [
1006+
"code": code,
1007+
"recipientId": recipientId,
1008+
"domain": domain
1009+
] as [String: Any]
1010+
Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
1011+
let responseData = handleResponse(response)
1012+
completion(responseData)
1013+
}
1014+
}
9861015
}

0 commit comments

Comments
 (0)