Skip to content

Commit 107b35d

Browse files
authored
Merge pull request #23 from devwaseem/main
Refactored Authentication code
2 parents 81e93ea + d82175c commit 107b35d

File tree

9 files changed

+198
-55
lines changed

9 files changed

+198
-55
lines changed

Expenso.xcodeproj/project.pbxproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
738B1C9025C680D20067407B /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B1C8F25C680D20067407B /* AboutView.swift */; };
4646
73998C5525DA5578007D735B /* AttachmentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73998C5425DA5578007D735B /* AttachmentHandler.swift */; };
4747
739DFF7B25DC1E3C005BD5C8 /* AuthenticateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739DFF7A25DC1E3C005BD5C8 /* AuthenticateView.swift */; };
48+
73BD61BD25DEBC3F00E85F67 /* ExpenseFilterChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BD61BC25DEBC3F00E85F67 /* ExpenseFilterChartView.swift */; };
49+
753CBDD325F36864005762B8 /* BiometricAuthUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753CBDD225F36864005762B8 /* BiometricAuthUtility.swift */; };
50+
75C6B48025F37FD20079BCFC /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C6B47F25F37FD20079BCFC /* AuthenticationViewModel.swift */; };
4851
73E688DA25FF32790000462A /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73E688D925FF32790000462A /* ChartView.swift */; };
4952
FF29C9FFAF86E0452686C5D0 /* Pods_Expenso.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B0E373FB89DD0864398FEE7 /* Pods_Expenso.framework */; };
5053
/* End PBXBuildFile section */
@@ -92,6 +95,9 @@
9295
738B1C8F25C680D20067407B /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
9396
73998C5425DA5578007D735B /* AttachmentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentHandler.swift; sourceTree = "<group>"; };
9497
739DFF7A25DC1E3C005BD5C8 /* AuthenticateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticateView.swift; sourceTree = "<group>"; };
98+
73BD61BC25DEBC3F00E85F67 /* ExpenseFilterChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseFilterChartView.swift; sourceTree = "<group>"; };
99+
753CBDD225F36864005762B8 /* BiometricAuthUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricAuthUtility.swift; sourceTree = "<group>"; };
100+
75C6B47F25F37FD20079BCFC /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
95101
73E688D925FF32790000462A /* ChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = "<group>"; };
96102
B07E4D1AF457AB52E048A8B5 /* Pods-Expenso.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Expenso.release.xcconfig"; path = "Target Support Files/Pods-Expenso/Pods-Expenso.release.xcconfig"; sourceTree = "<group>"; };
97103
/* End PBXFileReference section */
@@ -137,6 +143,7 @@
137143
738B1C1625C65DFE0067407B /* Expenso */ = {
138144
isa = PBXGroup;
139145
children = (
146+
75C6B48225F37FF60079BCFC /* Utilities */,
140147
739DFF7925DC1E23005BD5C8 /* Authenticate */,
141148
736C720825CFD82900720DEA /* Animations */,
142149
738B1C8E25C680C50067407B /* About */,
@@ -265,10 +272,19 @@
265272
isa = PBXGroup;
266273
children = (
267274
739DFF7A25DC1E3C005BD5C8 /* AuthenticateView.swift */,
275+
75C6B47F25F37FD20079BCFC /* AuthenticationViewModel.swift */,
268276
);
269277
path = Authenticate;
270278
sourceTree = "<group>";
271279
};
280+
75C6B48225F37FF60079BCFC /* Utilities */ = {
281+
isa = PBXGroup;
282+
children = (
283+
753CBDD225F36864005762B8 /* BiometricAuthUtility.swift */,
284+
);
285+
path = Utilities;
286+
sourceTree = "<group>";
287+
};
272288
797DEE244C92A7FEC658698F /* Frameworks */ = {
273289
isa = PBXGroup;
274290
children = (
@@ -423,7 +439,9 @@
423439
738B1C4325C6629D0067407B /* ColorExtension.swift in Sources */,
424440
738B1C4025C662890067407B /* InterFontModifier.swift in Sources */,
425441
738B1C3C25C662580067407B /* Models.swift in Sources */,
442+
753CBDD325F36864005762B8 /* BiometricAuthUtility.swift in Sources */,
426443
738B1C7325C674320067407B /* ExpenseSettingsView.swift in Sources */,
444+
75C6B48025F37FD20079BCFC /* AuthenticationViewModel.swift in Sources */,
427445
738B1C4C25C663060067407B /* DismissKeyboard.swift in Sources */,
428446
738B1C4625C662D90067407B /* TextView.swift in Sources */,
429447
738B1C7B25C6752E0067407B /* AddExpenseViewModel.swift in Sources */,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>Expenso.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
12+
</dict>
13+
</dict>
14+
</plist>
Binary file not shown.

Expenso/Authenticate/AuthenticateView.swift

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,21 @@ import SwiftUI
99
import LocalAuthentication
1010

1111
struct AuthenticateView: View {
12-
13-
@State private var didAuthenticate = false
14-
15-
@State private var alertMsg = ""
16-
@State private var showAlert = false
12+
13+
@ObservedObject var viewModel: AuthenticationViewModel
1714

1815
var body: some View {
1916
NavigationView {
2017
ZStack {
2118
Color.primary_color.edgesIgnoringSafeArea(.all)
2219

2320
VStack {
24-
NavigationLink(destination: NavigationLazyView(ExpenseView()), isActive: $didAuthenticate, label: {})
21+
NavigationLink(destination: NavigationLazyView(ExpenseView()), isActive: $viewModel.didAuthenticate, label: {})
2522
Spacer()
2623
Image("pie_icon").resizable().frame(width: 120.0, height: 120.0)
2724
VStack(spacing: 16) {
2825
TextView(text: "\(APP_NAME) is locked", type: .body_1).foregroundColor(Color.text_primary_color).padding(.top, 20)
29-
Button(action: { self.authenticate() }, label: {
26+
Button(action: { viewModel.authenticate() }, label: {
3027
HStack {
3128
Spacer()
3229
TextView(text: "Unlock", type: .button).foregroundColor(Color.main_color)
@@ -46,36 +43,18 @@ struct AuthenticateView: View {
4643
}
4744
.navigationBarHidden(true)
4845
}
46+
.onAppear(perform: {
47+
viewModel.authenticate()
48+
})
4949
.navigationViewStyle(StackNavigationViewStyle())
5050
.navigationBarHidden(true)
5151
.navigationBarBackButtonHidden(true)
5252
}
5353

54-
func authenticate() {
55-
let context = LAContext()
56-
var error: NSError?
57-
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
58-
let reason = "Please authenticate yourself to unlock \(APP_NAME)"
59-
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
60-
DispatchQueue.main.async {
61-
if success { self.didAuthenticate = true }
62-
else { alertMsg = authenticationError?.localizedDescription ?? "Error"; showAlert = true }
63-
}
64-
}
65-
} else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
66-
let reason = "Please authenticate yourself to unlock \(APP_NAME)"
67-
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authenticationError in
68-
DispatchQueue.main.async {
69-
if success { self.didAuthenticate = true }
70-
else { alertMsg = authenticationError?.localizedDescription ?? "Error"; showAlert = true }
71-
}
72-
}
73-
}
74-
}
7554
}
7655

7756
struct AuthenticateView_Previews: PreviewProvider {
7857
static var previews: some View {
79-
AuthenticateView()
58+
AuthenticateView(viewModel: AuthenticationViewModel())
8059
}
8160
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// AuthenticationViewModel.swift
3+
// Expenso
4+
//
5+
// Created by Waseem Akram on 06/03/21.
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
class AuthenticationViewModel: ObservableObject {
12+
13+
var cancellableBiometricTask: AnyCancellable? = nil
14+
15+
var didAuthenticate = false
16+
var showAlert = false
17+
var alertMessage = String()
18+
19+
func authenticate(){
20+
didAuthenticate = false
21+
showAlert = false
22+
alertMessage = ""
23+
cancellableBiometricTask = BiometricAuthUtlity.shared.authenticate()
24+
.receive(on: DispatchQueue.main)
25+
.sink(receiveCompletion: { completion in
26+
switch completion {
27+
case .failure(let error):
28+
self.showAlert = true
29+
self.alertMessage = error.description
30+
default: return
31+
}
32+
}) { _ in
33+
self.didAuthenticate = true
34+
}
35+
}
36+
37+
deinit {
38+
cancellableBiometricTask = nil
39+
}
40+
}

Expenso/ExpenseSettings/ExpenseSettingsViewModel.swift

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@
66
//
77

88
import UIKit
9+
import Combine
910
import CoreData
1011
import LocalAuthentication
1112

1213
class ExpenseSettingsViewModel: ObservableObject {
1314

1415
var csvModelArr = [ExpenseCSVModel]()
1516

17+
var cancellableBiometricTask: AnyCancellable? = nil
18+
1619
@Published var currency = UserDefaults.standard.string(forKey: UD_EXPENSE_CURRENCY) ?? ""
1720
@Published var enableBiometric = UserDefaults.standard.bool(forKey: UD_USE_BIOMETRIC) {
1821
didSet {
19-
if enableBiometric { authBiometric() }
22+
if enableBiometric { authenticate() }
2023
else { UserDefaults.standard.setValue(false, forKey: UD_USE_BIOMETRIC) }
2124
}
2225
}
@@ -25,31 +28,23 @@ class ExpenseSettingsViewModel: ObservableObject {
2528
@Published var showAlert = false
2629

2730
init() {}
28-
29-
func authBiometric() {
30-
let scanner = LAContext()
31-
var error: NSError?
32-
if scanner.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
33-
scanner.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "To Unlock \(APP_NAME)") { (status, err) in
34-
if let err = err {
35-
DispatchQueue.main.async {
36-
self.enableBiometric = false
37-
self.alertMsg = err.localizedDescription
38-
self.showAlert = true
39-
}
40-
} else { UserDefaults.standard.setValue(true, forKey: UD_USE_BIOMETRIC) }
41-
}
42-
} else if scanner.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
43-
scanner.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "To Unlock \(APP_NAME)") { (status, err) in
44-
if let err = err {
45-
DispatchQueue.main.async {
46-
self.enableBiometric = false
47-
self.alertMsg = err.localizedDescription
48-
self.showAlert = true
49-
}
50-
} else { UserDefaults.standard.setValue(true, forKey: UD_USE_BIOMETRIC) }
31+
32+
func authenticate() {
33+
showAlert = false
34+
alertMsg = ""
35+
cancellableBiometricTask = BiometricAuthUtlity.shared.authenticate()
36+
.receive(on: DispatchQueue.main)
37+
.sink(receiveCompletion: { completion in
38+
switch completion {
39+
case .failure(let error):
40+
self.showAlert = true
41+
self.alertMsg = error.description
42+
self.enableBiometric = false
43+
default: return
44+
}
45+
}) { _ in
46+
UserDefaults.standard.setValue(true, forKey: UD_USE_BIOMETRIC)
5147
}
52-
}
5348
}
5449

5550
func getBiometricType() -> String {
@@ -113,4 +108,8 @@ class ExpenseSettingsViewModel: ObservableObject {
113108

114109
print(path ?? "not found")
115110
}
111+
112+
deinit {
113+
cancellableBiometricTask = nil
114+
}
116115
}

Expenso/ExpensoApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct ExpensoApp: App {
2525
var body: some Scene {
2626
WindowGroup {
2727
if UserDefaults.standard.bool(forKey: UD_USE_BIOMETRIC) {
28-
AuthenticateView()
28+
AuthenticateView(viewModel: AuthenticationViewModel())
2929
.environment(\.managedObjectContext, persistentContainer.viewContext)
3030
} else {
3131
ExpenseView()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// BiometricAuthUtility.swift
3+
// Expenso
4+
//
5+
// Created by Waseem Akram on 06/03/21.
6+
//
7+
8+
import Foundation
9+
import Combine
10+
import LocalAuthentication
11+
12+
struct BiometericAuthError: LocalizedError {
13+
14+
var description: String
15+
16+
init(description: String){
17+
self.description = description
18+
}
19+
20+
init(error: Error){
21+
self.description = error.localizedDescription
22+
}
23+
24+
var errorDescription: String?{
25+
return description
26+
}
27+
}
28+
29+
class BiometricAuthUtlity {
30+
31+
32+
static let shared = BiometricAuthUtlity()
33+
34+
private init(){}
35+
36+
/// Authenticate the user with device Authentication system.
37+
/// If the .deviceOwnerAuthenticationWithBiometrics is not available, it will fallback to .deviceOwnerAuthentication
38+
/// - Returns: future which passes `Bool` when the authentication suceeds or `BiometericAuthError` when failed to authenticate
39+
public func authenticate() -> Future<Bool, BiometericAuthError> {
40+
Future { promise in
41+
42+
func handleReply(success: Bool, error: Error?) -> Void {
43+
if let error = error {
44+
return promise(
45+
.failure(BiometericAuthError(error: error))
46+
)
47+
}
48+
49+
promise(.success(success))
50+
}
51+
52+
let context = LAContext()
53+
var error: NSError?
54+
let reason = "Please authenticate yourself to unlock \(APP_NAME)"
55+
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
56+
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: handleReply)
57+
} else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
58+
// fallback
59+
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason, reply: handleReply)
60+
}else{
61+
//cannot evaluate
62+
let error = BiometericAuthError(description: "Something went wrong while authenticating. Please try again")
63+
promise(.failure(error))
64+
}
65+
}
66+
}
67+
68+
69+
}

Pods/Pods.xcodeproj/xcuserdata/waseemakram.xcuserdatad/xcschemes/xcschememanagement.plist

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)