From 661b3535aeaa089045538bcd1a980d4deb165cc0 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Thu, 28 Dec 2023 11:47:25 +0700 Subject: [PATCH] feat: ios: authenticate user before clearing logs --- ios/PolkadotVault.xcodeproj/project.pbxproj | 14 +++++++- .../DevicePasscodeAuthenticator.swift | 34 +++++++++++++++++++ ios/PolkadotVault/Core/ServiceLocator.swift | 1 + .../Screens/Logs/Views/LogNoteModal.swift | 2 +- .../Screens/Logs/Views/LogsListView.swift | 11 ++++-- 5 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 ios/PolkadotVault/Core/Authentication/DevicePasscodeAuthenticator.swift diff --git a/ios/PolkadotVault.xcodeproj/project.pbxproj b/ios/PolkadotVault.xcodeproj/project.pbxproj index 62c113693e..5a38c4ffa0 100644 --- a/ios/PolkadotVault.xcodeproj/project.pbxproj +++ b/ios/PolkadotVault.xcodeproj/project.pbxproj @@ -131,6 +131,7 @@ 6D5801E5289937BA006C41D8 /* ConnectivityMonitoringAssemblerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5801E4289937BA006C41D8 /* ConnectivityMonitoringAssemblerTests.swift */; }; 6D5801E7289937C0006C41D8 /* ConnectivityMonitoringAssembler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5801E6289937C0006C41D8 /* ConnectivityMonitoringAssembler.swift */; }; 6D5DB5E82B3C61A200EF82AB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5DB5E72B3C61A200EF82AB /* AppDelegate.swift */; }; + 6D5DB5EB2B3D1A9500EF82AB /* DevicePasscodeAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5DB5EA2B3D1A9500EF82AB /* DevicePasscodeAuthenticator.swift */; }; 6D5DCA032A7CFA2E0050B101 /* ExportKeysSelectionModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5DCA022A7CFA2E0050B101 /* ExportKeysSelectionModal.swift */; }; 6D5FDDCC2977D08E0076C1C4 /* LogNoteModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5FDDCB2977D08E0076C1C4 /* LogNoteModal.swift */; }; 6D5FDDD02977DBE00076C1C4 /* HiddenTextEditorBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5FDDCF2977DBE00076C1C4 /* HiddenTextEditorBackgroundModifier.swift */; }; @@ -509,6 +510,7 @@ 6D5801E4289937BA006C41D8 /* ConnectivityMonitoringAssemblerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityMonitoringAssemblerTests.swift; sourceTree = ""; }; 6D5801E6289937C0006C41D8 /* ConnectivityMonitoringAssembler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityMonitoringAssembler.swift; sourceTree = ""; }; 6D5DB5E72B3C61A200EF82AB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6D5DB5EA2B3D1A9500EF82AB /* DevicePasscodeAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicePasscodeAuthenticator.swift; sourceTree = ""; }; 6D5DCA022A7CFA2E0050B101 /* ExportKeysSelectionModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportKeysSelectionModal.swift; sourceTree = ""; }; 6D5FDDCB2977D08E0076C1C4 /* LogNoteModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogNoteModal.swift; sourceTree = ""; }; 6D5FDDCF2977DBE00076C1C4 /* HiddenTextEditorBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTextEditorBackgroundModifier.swift; sourceTree = ""; }; @@ -886,11 +888,11 @@ isa = PBXGroup; children = ( 2DE72BC126A588C7002BB752 /* PolkadotVaultApp.swift */, + 6DEEA87D28AFBF5D00371ECA /* libsigner.a */, 6D06CD272AFD2EF000FAB275 /* Configuration */, 6D8045DA28D0840500237F8C /* RustDataExtensions */, 6DA2ACA02939CB3000AAEADC /* Helpers */, 6D2D244928CCAD3700862726 /* Backend */, - 6DEEA87D28AFBF5D00371ECA /* libsigner.a */, 6DFAF36C28AF845F0048763B /* Stubs */, 6D5801CF28991306006C41D8 /* Protocols */, 6D5801CE28991301006C41D8 /* Core */, @@ -1272,6 +1274,7 @@ 6D5801CE28991301006C41D8 /* Core */ = { isa = PBXGroup; children = ( + 6D5DB5E92B3D1A8700EF82AB /* Authentication */, 6D5DB5E72B3C61A200EF82AB /* AppDelegate.swift */, 6D6430F528CB460A00342E37 /* ServiceLocator.swift */, 6DFE588C297A72B1002BFDBF /* JailbreakDetectionPublisher.swift */, @@ -1341,6 +1344,14 @@ path = Connectivity; sourceTree = ""; }; + 6D5DB5E92B3D1A8700EF82AB /* Authentication */ = { + isa = PBXGroup; + children = ( + 6D5DB5EA2B3D1A9500EF82AB /* DevicePasscodeAuthenticator.swift */, + ); + path = Authentication; + sourceTree = ""; + }; 6D5DCA012A7CFA1E0050B101 /* ExportKeys */ = { isa = PBXGroup; children = ( @@ -2521,6 +2532,7 @@ 6DF91F4029C06B70000A6BB2 /* VerifierValue+Show.swift in Sources */, 6D2D245228CE5F2D00862726 /* KeyDetailsPublicKeyView.swift in Sources */, 6DD4A6A629E9480700FA6746 /* KeyListService.swift in Sources */, + 6D5DB5EB2B3D1A9500EF82AB /* DevicePasscodeAuthenticator.swift in Sources */, 6D95E97328B4F42300E28A11 /* ActionButton.swift in Sources */, 2DAA829D27885A67002917C0 /* TCMeta.swift in Sources */, 2DA5F86027566C3600D8DD29 /* TCAuthorPublicKey.swift in Sources */, diff --git a/ios/PolkadotVault/Core/Authentication/DevicePasscodeAuthenticator.swift b/ios/PolkadotVault/Core/Authentication/DevicePasscodeAuthenticator.swift new file mode 100644 index 0000000000..eaa0cb3498 --- /dev/null +++ b/ios/PolkadotVault/Core/Authentication/DevicePasscodeAuthenticator.swift @@ -0,0 +1,34 @@ +// +// DevicePasscodeAuthenticator.swift +// PolkadotVault +// +// Created by Krzysztof Rodak on 28/12/2023. +// + +import Foundation +import LocalAuthentication + +protocol DevicePasscodeAuthenticatorProtocol { + /// This methods trigger passcode screen to authenticate user, if failed, will bring back "Unlock app" screen + /// + /// As we can't use `LAContext` to properly evaluate user authentication, as this would trigger biometry check + /// if set up. Hence we need to rely on fetching some seed from Keychain and with assumption that authentication + /// is used only when some seed is present this satisfies our requirements + /// - Returns: whether user is authenticated or not + func authenticateUser() -> Bool +} + +final class DevicePasscodeAuthenticator: DevicePasscodeAuthenticatorProtocol { + private let seedsMediator: SeedsMediating + + init( + seedsMediator: SeedsMediating = ServiceLocator.seedsMediator + ) { + self.seedsMediator = seedsMediator + } + + func authenticateUser() -> Bool { + guard let seedName = seedsMediator.seedNames.first else { return true } + return !seedsMediator.getSeed(seedName: seedName).isEmpty + } +} diff --git a/ios/PolkadotVault/Core/ServiceLocator.swift b/ios/PolkadotVault/Core/ServiceLocator.swift index d624768cae..811e20e2b9 100644 --- a/ios/PolkadotVault/Core/ServiceLocator.swift +++ b/ios/PolkadotVault/Core/ServiceLocator.swift @@ -17,4 +17,5 @@ enum ServiceLocator { static var onboardingMediator: OnboardingMediator = OnboardingMediator() static var networkColorsGenerator = UnknownNetworkColorsGenerator() + static var devicePasscodeAuthenticator: DevicePasscodeAuthenticatorProtocol = DevicePasscodeAuthenticator() } diff --git a/ios/PolkadotVault/Screens/Logs/Views/LogNoteModal.swift b/ios/PolkadotVault/Screens/Logs/Views/LogNoteModal.swift index 2980f700ee..dc16e7fd70 100644 --- a/ios/PolkadotVault/Screens/Logs/Views/LogNoteModal.swift +++ b/ios/PolkadotVault/Screens/Logs/Views/LogNoteModal.swift @@ -117,7 +117,7 @@ extension LogNoteModal { case .success: isPresented = false case let .failure(error): - presentableError = .init(title: error.description) + presentableError = .alertError(message: error.description) isPresentingError = true } } diff --git a/ios/PolkadotVault/Screens/Logs/Views/LogsListView.swift b/ios/PolkadotVault/Screens/Logs/Views/LogsListView.swift index 3ae70eb323..9120c88de3 100644 --- a/ios/PolkadotVault/Screens/Logs/Views/LogsListView.swift +++ b/ios/PolkadotVault/Screens/Logs/Views/LogsListView.swift @@ -99,13 +99,17 @@ extension LogsListView { @Published var isPresentingError: Bool = false @Published var presentableError: ErrorBottomModalViewModel = .noNetworksAvailable() private let logsService: LogsService + private let devicePasscodeAuthenticator: DevicePasscodeAuthenticatorProtocol private let renderableBuilder: LogEntryRenderableBuilder init( logsService: LogsService = LogsService(), + devicePasscodeAuthenticator: DevicePasscodeAuthenticatorProtocol = ServiceLocator + .devicePasscodeAuthenticator, renderableBuilder: LogEntryRenderableBuilder = LogEntryRenderableBuilder() ) { self.logsService = logsService + self.devicePasscodeAuthenticator = devicePasscodeAuthenticator self.renderableBuilder = renderableBuilder } @@ -117,7 +121,7 @@ extension LogsListView { self.logs = logs renderables = renderableBuilder.build(logs) case let .failure(error): - presentableError = .init(title: error.description) + presentableError = .alertError(message: error.description) isPresentingError = true } } @@ -148,7 +152,7 @@ extension LogsListView { isPresentingDetails = true case let .failure(error): selectedDetails = nil - presentableError = .init(title: error.description) + presentableError = .alertError(message: error.description) isPresentingError = true } } @@ -159,13 +163,14 @@ extension LogsListView { } func clearLogsAction() { + guard devicePasscodeAuthenticator.authenticateUser() else { return } logsService.cleaLogHistory { [weak self] result in guard let self else { return } switch result { case .success: loadData() case let .failure(error): - presentableError = .init(title: error.description) + presentableError = .alertError(message: error.description) isPresentingError = true } }