mobile: local authentication (#696)

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
JRoberts
2022-05-27 18:21:35 +04:00
committed by GitHub
parent 387aec8593
commit 79d9e90ab7
14 changed files with 540 additions and 78 deletions
+3 -1
View File
@@ -12,11 +12,13 @@ struct ContentView: View {
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
@State private var showNotificationAlert = false
@Binding var userAuthorized: Bool?
var body: some View {
ZStack {
if let step = chatModel.onboardingStage {
if case .onboardingComplete = step,
if userAuthorized == true,
case .onboardingComplete = step,
let user = chatModel.currentUser {
ZStack(alignment: .top) {
ChatListView(user: user)
+45 -2
View File
@@ -15,6 +15,9 @@ struct SimpleXApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var chatModel = ChatModel.shared
@Environment(\.scenePhase) var scenePhase
@State private var userAuthorized: Bool? = nil
@State private var doAuthenticate: Bool? = nil
@State private var lastLA: Double? = nil
init() {
hs_init(0, nil)
@@ -24,7 +27,7 @@ struct SimpleXApp: App {
var body: some Scene {
return WindowGroup {
ContentView()
ContentView(userAuthorized: $userAuthorized)
.environmentObject(chatModel)
.onOpenURL { url in
logger.debug("ContentView.onOpenURL: \(url)")
@@ -32,14 +35,54 @@ struct SimpleXApp: App {
}
.onAppear() {
initializeChat()
doAuthenticate = true
}
.onChange(of: scenePhase) { phase in
logger.debug("scenePhase \(String(describing: scenePhase))")
setAppState(phase)
if phase == .background {
switch (phase) {
case .background:
BGManager.shared.schedule()
doAuthenticate = true
case .inactive:
authenticateUser()
case .active:
authenticateUser()
default:
break
}
}
}
}
private func authenticateUser() {
if doAuthenticate == true,
authenticationExpired() {
doAuthenticate = false
userAuthorized = false
authenticate() { laResult in
switch (laResult) {
case .success:
userAuthorized = true
lastLA = ProcessInfo.processInfo.systemUptime
case .failed:
laFailedAlert()
case .unavailable:
userAuthorized = true
laUnavailableAlert()
}
}
}
}
private func authenticationExpired() -> Bool {
if (lastLA == nil) {
return true
}
else if let lastLA = lastLA, ProcessInfo.processInfo.systemUptime - lastLA >= 30 {
return true
} else {
return false
}
}
}
@@ -0,0 +1,51 @@
//
// LocalAuthenticationUtils.swift
// SimpleX (iOS)
//
// Created by Efim Poberezkin on 26.05.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import LocalAuthentication
enum LAResult {
case success
case failed(authError: String?)
case unavailable(authError: String?)
}
func authenticate(completed: @escaping (LAResult) -> Void) {
let laContext = LAContext()
var authAvailabilityError: NSError?
if laContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authAvailabilityError) {
let reason = "Access chats"
laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authError in
DispatchQueue.main.async {
if success {
completed(LAResult.success)
} else {
logger.error("authentication error: \(authError.debugDescription)")
completed(LAResult.failed(authError: authError?.localizedDescription))
}
}
}
} else {
logger.error("authentication availability error: \(authAvailabilityError.debugDescription)")
completed(LAResult.unavailable(authError: authAvailabilityError?.localizedDescription))
}
}
func laFailedAlert() {
AlertManager.shared.showAlertMsg(
title: "Authentication failed",
message: "You could not be verified; please try again."
)
}
func laUnavailableAlert() {
AlertManager.shared.showAlertMsg(
title: "Authentication unavailable",
message: "Your device is not configured for authentication."
)
}
@@ -100,6 +100,8 @@
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; };
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
@@ -221,6 +223,8 @@
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; };
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; };
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = "<group>"; };
648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = "<group>"; };
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
@@ -241,6 +245,7 @@
buildActionMask = 2147483647;
files = (
64A6908928376BBA0076573F /* libHSsimplex-chat-2.1.0-KOac7DFCSQz9HzISDnAYtC-ghc8.10.7.a in Frameworks */,
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */,
64A6908528376BBA0076573F /* libgmpxx.a in Frameworks */,
64A6908728376BBA0076573F /* libgmp.a in Frameworks */,
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */,
@@ -333,6 +338,7 @@
5C764E7A279C71D4000C6508 /* Frameworks */ = {
isa = PBXGroup;
children = (
646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */,
5CDCAD6028187D7900503DA2 /* libz.tbd */,
5CDCAD5E28187D4A00503DA2 /* libiconv.tbd */,
5C764E7C279C71DB000C6508 /* libz.tbd */,
@@ -369,6 +375,7 @@
648010AA281ADD15009009B9 /* CIFileView.swift */,
6454036E2822A9750090DDFF /* ComposeFileView.swift */,
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */,
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -714,6 +721,7 @@
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */,
5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */,
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */,
@@ -950,6 +958,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
INFOPLIST_KEY_NSFaceIDUsageDescription = "SimpleX uses Face ID for local authentication";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SimpleX needs microphone access for audio and video calls.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SimpleX needs access to Photo Library for saving captured and received media";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -992,6 +1001,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
INFOPLIST_KEY_NSFaceIDUsageDescription = "SimpleX uses Face ID for local authentication";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SimpleX needs microphone access for audio and video calls.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SimpleX needs access to Photo Library for saving captured and received media";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;