From d6579657492aa74f8138fe7ca3177b5e535915ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Guly=C3=A1s?= Date: Fri, 21 Jun 2024 16:13:16 +0200 Subject: [PATCH 1/2] NEVISACCESSAPP-5943: Updated NMA SDK to 3.7.0 - Updated Nevis Mobile Authentication SDK to 3.7.0 - Updated bundle dependencies --- Gemfile.lock | 85 ++++++++++++----------- NevisExampleApp.xcodeproj/project.pbxproj | 68 ++++++++++++++++++ Podfile | 4 +- Podfile.lock | 14 ++-- 4 files changed, 123 insertions(+), 48 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d95b2bc..d9c1608 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,11 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.1.3) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -13,32 +15,32 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.887.0) - aws-sdk-core (3.191.0) + aws-partitions (1.946.0) + aws-sdk-core (3.197.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.77.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-kms (1.85.0) + aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.143.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.152.3) + aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) - bigdecimal (3.1.6) + bigdecimal (3.1.8) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -81,20 +83,19 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) connection_pool (2.4.1) declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.109.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -123,15 +124,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -152,10 +153,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -164,11 +165,12 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-firebase_app_distribution (0.9.1) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) - ffi (1.16.3) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86_64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -192,12 +194,12 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.7.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) + google-cloud-errors (1.4.0) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -213,42 +215,45 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) - jwt (2.7.1) - mini_magick (4.12.0) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.1) mini_mime (1.1.5) - minitest (5.22.2) + minitest (5.24.0) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.1) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - optparse (0.4.0) + nkf (0.2.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (4.0.7) - rake (13.1.0) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.2.9) + strscan rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.18.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -256,6 +261,7 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -284,6 +290,7 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS + arm64-darwin-21 x86_64-darwin-20 x86_64-darwin-22 @@ -293,4 +300,4 @@ DEPENDENCIES fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.3.7 + 2.5.6 diff --git a/NevisExampleApp.xcodeproj/project.pbxproj b/NevisExampleApp.xcodeproj/project.pbxproj index fc03108..331e3a1 100644 --- a/NevisExampleApp.xcodeproj/project.pbxproj +++ b/NevisExampleApp.xcodeproj/project.pbxproj @@ -110,9 +110,11 @@ 38E1CBF32A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E1CBF22A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift */; }; 38FF9DC5292E09E70027C09E /* PinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FF9DC4292E09E70027C09E /* PinView.swift */; }; 38FF9DC7292E0B160027C09E /* LoggingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FF9DC6292E0B160027C09E /* LoggingView.swift */; }; + 9BE0F5E78A6A916FF7E09A96 /* Pods_NevisExampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NevisExampleApp.release.xcconfig"; path = "Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp.release.xcconfig"; sourceTree = ""; }; 382FE1D62BADD5FC00999625 /* ConfirmationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationPresenter.swift; sourceTree = ""; }; 382FE1D82BADD60400999625 /* ConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationScreen.swift; sourceTree = ""; }; 3833EB192685EA03002C5E0C /* NevisExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NevisExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -220,6 +222,8 @@ 38E1CBF22A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicePasscodeUserVerifierImpl.swift; sourceTree = ""; }; 38FF9DC4292E09E70027C09E /* PinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinView.swift; sourceTree = ""; }; 38FF9DC6292E0B160027C09E /* LoggingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingView.swift; sourceTree = ""; }; + 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NevisExampleApp.debug.xcconfig"; path = "Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp.debug.xcconfig"; sourceTree = ""; }; + F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NevisExampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -227,6 +231,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9BE0F5E78A6A916FF7E09A96 /* Pods_NevisExampleApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -247,6 +252,8 @@ children = ( 3833EB1B2685EA03002C5E0C /* NevisExampleApp */, 3833EB1A2685EA03002C5E0C /* Products */, + A651FDC4F5C71ADD993EA999 /* Pods */, + 8E9E59B3AFC9FF84E096D1EB /* Frameworks */, ); sourceTree = ""; }; @@ -664,6 +671,24 @@ path = "Username Password Login"; sourceTree = ""; }; + 8E9E59B3AFC9FF84E096D1EB /* Frameworks */ = { + isa = PBXGroup; + children = ( + F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A651FDC4F5C71ADD993EA999 /* Pods */ = { + isa = PBXGroup; + children = ( + 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */, + 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -671,10 +696,12 @@ isa = PBXNativeTarget; buildConfigurationList = 3833EB2D2685EA03002C5E0C /* Build configuration list for PBXNativeTarget "NevisExampleApp" */; buildPhases = ( + 79CB52AC7DDC23DF75949849 /* [CP] Check Pods Manifest.lock */, 3867C1972685EE1F00A8B98B /* Swiftformat */, 3833EB152685EA03002C5E0C /* Sources */, 3833EB162685EA03002C5E0C /* Frameworks */, 3833EB172685EA03002C5E0C /* Resources */, + B33D1E141A1B58EE4EBF5ABB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -753,6 +780,45 @@ shellPath = /bin/sh; shellScript = "if which swiftformat >/dev/null; then\n echo \"SwiftFormat version: $(swiftformat --version)\"\n swiftformat \"${SRCROOT}/\"\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n"; }; + 79CB52AC7DDC23DF75949849 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NevisExampleApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B33D1E141A1B58EE4EBF5ABB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1005,6 +1071,7 @@ }; 3833EB2E2685EA03002C5E0C /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1036,6 +1103,7 @@ }; 3833EB2F2685EA03002C5E0C /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/Podfile b/Podfile index b5120e7..ffa3452 100644 --- a/Podfile +++ b/Podfile @@ -14,8 +14,8 @@ target 'NevisExampleApp' do pod 'Swinject', '= 2.8.3' pod 'SwinjectAutoregistration', '= 2.8.3' pod 'Then', '= 3.0.0' - pod 'NevisMobileAuthentication', '~> 3.6.0', :configurations => ['Release'] - pod 'NevisMobileAuthentication-Debug', '~> 3.6.0', :configurations => ['Debug'] + pod 'NevisMobileAuthentication', '~> 3.7.0', :configurations => ['Release'] + pod 'NevisMobileAuthentication-Debug', '~> 3.7.0', :configurations => ['Debug'] end post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock index a5d6cbf..06efd06 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -4,8 +4,8 @@ PODS: - KRProgressHUD (3.4.8): - KRActivityIndicatorView (= 3.0.8) - MercariQRScanner (1.9.0) - - NevisMobileAuthentication (3.6.4) - - NevisMobileAuthentication-Debug (3.6.4) + - NevisMobileAuthentication (3.7.0) + - NevisMobileAuthentication-Debug (3.7.0) - Swinject (2.8.3) - SwinjectAutoregistration (2.8.3): - Swinject (~> 2.8.3) @@ -15,8 +15,8 @@ DEPENDENCIES: - FittedSheets (= 2.6.1) - KRProgressHUD (= 3.4.8) - MercariQRScanner (= 1.9.0) - - NevisMobileAuthentication (~> 3.6.0) - - NevisMobileAuthentication-Debug (~> 3.6.0) + - NevisMobileAuthentication (~> 3.7.0) + - NevisMobileAuthentication-Debug (~> 3.7.0) - Swinject (= 2.8.3) - SwinjectAutoregistration (= 2.8.3) - Then (= 3.0.0) @@ -38,12 +38,12 @@ SPEC CHECKSUMS: KRActivityIndicatorView: c5c503bf56e54834382dd926b64ff47287ca0006 KRProgressHUD: d0fb4e72428d312eb6966e84c543ea58fb1efbc7 MercariQRScanner: e5aea873969af59b09b4cc411fc1c5b6293dba44 - NevisMobileAuthentication: ddf68900d84b25df43397daf4e11231eed96e3ff - NevisMobileAuthentication-Debug: 2d5632961696c6ca3b246c45c50e438c4603a36a + NevisMobileAuthentication: 0b4094d60be9ba4b5a298a29b6f5ee33b5405a83 + NevisMobileAuthentication-Debug: e458515add04dc3fe7cda6e18a352c96d237180e Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 SwinjectAutoregistration: e3ee378dc458acf78d176fc7fec3902603452667 Then: 844265ae87834bbe1147d91d5d41a404da2ec27d -PODFILE CHECKSUM: 199ed59c8405e47365ff123e078c1c2e5ab4c80e +PODFILE CHECKSUM: 9282922e44c6adbf6d9898144f8c8c374462ffc3 COCOAPODS: 1.15.2 From 774de84f9a895eca47f67ee96ad6e89078de9d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Guly=C3=A1s?= Date: Fri, 5 Jul 2024 13:20:02 +0200 Subject: [PATCH 2/2] NEVISACCESSAPP-5981: Added Password authenticator capabilities --- NevisExampleApp.xcodeproj/project.pbxproj | 174 +++---- .../xcschemes/NevisExampleApp.xcscheme | 2 +- .../Model/AppConfiguration.swift | 6 + .../Coordinator/App/AppCoordinator.swift | 4 +- .../Coordinator/App/AppCoordinatorImpl.swift | 24 +- .../Dependency Provider/AppAssembly.swift | 51 +- NevisExampleApp/Error/AppError.swift | 4 + .../Interaction/AccountSelectorImpl.swift | 7 +- ...henticationAuthenticatorSelectorImpl.swift | 63 --- .../AuthenticatorSelectorImpl.swift | 93 ++++ .../OutOfBandOperationHandlerImpl.swift | 14 + .../Password/PasswordChangerImpl.swift | 58 +++ .../Password/PasswordEnroller.swift | 56 +++ .../Password/PasswordUserVerifier.swift | 52 +++ .../{ => Pin}/PinChangerImpl.swift | 4 +- .../{ => Pin}/PinEnrollerImpl.swift | 4 +- .../{ => Pin}/PinUserVerifierImpl.swift | 4 +- ...egistrationAuthenticatorSelectorImpl.swift | 60 --- NevisExampleApp/Model/AuthenticatorItem.swift | 2 +- NevisExampleApp/Model/Operation.swift | 7 +- .../Resources/ConfigAuthenticationCloud.plist | 8 + .../Resources/ConfigIdentitySuite.plist | 7 + .../Resources/en.lproj/Localizable.strings | 29 +- .../AuthCloudApiRegistrationPresenter.swift | 7 + .../Credential/CredentialPresenter.swift | 440 ++++++++++++++++++ .../CredentialScreen.swift} | 62 +-- .../CredentialView.swift} | 6 +- .../CredentialProtectionInformation.swift} | 8 +- .../Screens/Home/HomePresenter.swift | 62 ++- NevisExampleApp/Screens/Home/HomeScreen.swift | 19 +- .../Screens/Pin/PinPresenter.swift | 275 ----------- .../SelectAccountPresenter.swift | 38 ++ .../UsernamePasswordLoginPresenter.swift | 7 + .../Extensions/Authenticator+Extension.swift | 2 + .../AuthenticatorAaid+Extensions.swift | 29 ++ ...henticatorProtectionStatus+Extension.swift | 37 ++ ...henticatorProtectionStatus+Extension.swift | 14 +- .../Utility/Localization/Strings.swift | 260 ++++++++--- .../Validation/AuthenticatorValidator.swift | 72 +++ 39 files changed, 1391 insertions(+), 680 deletions(-) delete mode 100644 NevisExampleApp/Interaction/AuthenticationAuthenticatorSelectorImpl.swift create mode 100644 NevisExampleApp/Interaction/AuthenticatorSelectorImpl.swift create mode 100644 NevisExampleApp/Interaction/Password/PasswordChangerImpl.swift create mode 100644 NevisExampleApp/Interaction/Password/PasswordEnroller.swift create mode 100644 NevisExampleApp/Interaction/Password/PasswordUserVerifier.swift rename NevisExampleApp/Interaction/{ => Pin}/PinChangerImpl.swift (92%) rename NevisExampleApp/Interaction/{ => Pin}/PinEnrollerImpl.swift (90%) rename NevisExampleApp/Interaction/{ => Pin}/PinUserVerifierImpl.swift (90%) delete mode 100644 NevisExampleApp/Interaction/RegistrationAuthenticatorSelectorImpl.swift create mode 100644 NevisExampleApp/Screens/Credential/CredentialPresenter.swift rename NevisExampleApp/Screens/{Pin/PinScreen.swift => Credential/CredentialScreen.swift} (64%) rename NevisExampleApp/Screens/{Pin/PinView.swift => Credential/CredentialView.swift} (63%) rename NevisExampleApp/Screens/{Pin/Model/PinProtectionInformation.swift => Credential/Model/CredentialProtectionInformation.swift} (68%) delete mode 100644 NevisExampleApp/Screens/Pin/PinPresenter.swift create mode 100644 NevisExampleApp/Utility/Extensions/AuthenticatorAaid+Extensions.swift create mode 100644 NevisExampleApp/Utility/Extensions/PasswordAuthenticatorProtectionStatus+Extension.swift create mode 100644 NevisExampleApp/Utility/Validation/AuthenticatorValidator.swift diff --git a/NevisExampleApp.xcodeproj/project.pbxproj b/NevisExampleApp.xcodeproj/project.pbxproj index 331e3a1..6709164 100644 --- a/NevisExampleApp.xcodeproj/project.pbxproj +++ b/NevisExampleApp.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 06000BAC2C37DDDD00AB53A1 /* PasswordAuthenticatorProtectionStatus+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06000BAB2C37DDDD00AB53A1 /* PasswordAuthenticatorProtectionStatus+Extension.swift */; }; + 06000BAE2C37ECD800AB53A1 /* PasswordChangerImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06000BAD2C37ECD800AB53A1 /* PasswordChangerImpl.swift */; }; + 06000BB02C37EDA100AB53A1 /* PasswordEnroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06000BAF2C37EDA100AB53A1 /* PasswordEnroller.swift */; }; + 06000BB22C37EE6A00AB53A1 /* PasswordUserVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06000BB12C37EE6A00AB53A1 /* PasswordUserVerifier.swift */; }; + 06471B152C29685C006879F9 /* AuthenticatorAaid+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06471B142C29685C006879F9 /* AuthenticatorAaid+Extensions.swift */; }; + 068E9ABC2C2D60BA0059E33C /* AuthenticatorValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068E9ABB2C2D60BA0059E33C /* AuthenticatorValidator.swift */; }; 382FE1D72BADD5FC00999625 /* ConfirmationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382FE1D62BADD5FC00999625 /* ConfirmationPresenter.swift */; }; 382FE1D92BADD60400999625 /* ConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382FE1D82BADD60400999625 /* ConfirmationScreen.swift */; }; 3833EB262685EA03002C5E0C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3833EB252685EA03002C5E0C /* Assets.xcassets */; }; @@ -29,8 +35,7 @@ 3859EAF9292CF8A800013472 /* ErrorHandlerChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAF3292CF8A800013472 /* ErrorHandlerChain.swift */; }; 3859EAFA292CF8A800013472 /* ErrorHandlerChainImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAF4292CF8A800013472 /* ErrorHandlerChainImpl.swift */; }; 3859EB36292CF92000013472 /* PinUserVerifierImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAFC292CF92000013472 /* PinUserVerifierImpl.swift */; }; - 3859EB37292CF92000013472 /* RegistrationAuthenticatorSelectorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAFD292CF92000013472 /* RegistrationAuthenticatorSelectorImpl.swift */; }; - 3859EB38292CF92000013472 /* AuthenticationAuthenticatorSelectorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAFE292CF92000013472 /* AuthenticationAuthenticatorSelectorImpl.swift */; }; + 3859EB38292CF92000013472 /* AuthenticatorSelectorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAFE292CF92000013472 /* AuthenticatorSelectorImpl.swift */; }; 3859EB39292CF92000013472 /* PinChangerImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EAFF292CF92000013472 /* PinChangerImpl.swift */; }; 3859EB3A292CF92000013472 /* OutOfBandOperationHandlerImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB00292CF92000013472 /* OutOfBandOperationHandlerImpl.swift */; }; 3859EB3B292CF92000013472 /* AccountSelectorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB01292CF92000013472 /* AccountSelectorImpl.swift */; }; @@ -39,9 +44,9 @@ 3859EB3E292CF92000013472 /* BiometricUserVerifierImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB04292CF92000013472 /* BiometricUserVerifierImpl.swift */; }; 3859EB3F292CF92000013472 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB07292CF92000013472 /* HomeScreen.swift */; }; 3859EB40292CF92000013472 /* HomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB08292CF92000013472 /* HomePresenter.swift */; }; - 3859EB41292CF92000013472 /* PinPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0A292CF92000013472 /* PinPresenter.swift */; }; - 3859EB42292CF92000013472 /* PinScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0B292CF92000013472 /* PinScreen.swift */; }; - 3859EB43292CF92000013472 /* PinProtectionInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0D292CF92000013472 /* PinProtectionInformation.swift */; }; + 3859EB41292CF92000013472 /* CredentialPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0A292CF92000013472 /* CredentialPresenter.swift */; }; + 3859EB42292CF92000013472 /* CredentialScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0B292CF92000013472 /* CredentialScreen.swift */; }; + 3859EB43292CF92000013472 /* CredentialProtectionInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0D292CF92000013472 /* CredentialProtectionInformation.swift */; }; 3859EB44292CF92000013472 /* ResultPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB0F292CF92000013472 /* ResultPresenter.swift */; }; 3859EB45292CF92000013472 /* ResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB10292CF92000013472 /* ResultScreen.swift */; }; 3859EB46292CF92000013472 /* ChangeDeviceInformationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3859EB12292CF92000013472 /* ChangeDeviceInformationScreen.swift */; }; @@ -108,13 +113,17 @@ 38DABD6A29D585C0008B8116 /* LoginValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DABD6929D585C0008B8116 /* LoginValidator.swift */; }; 38E17EB02A28DD7200441821 /* Authenticator+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E17EAF2A28DD7200441821 /* Authenticator+Extension.swift */; }; 38E1CBF32A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E1CBF22A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift */; }; - 38FF9DC5292E09E70027C09E /* PinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FF9DC4292E09E70027C09E /* PinView.swift */; }; + 38FF9DC5292E09E70027C09E /* CredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FF9DC4292E09E70027C09E /* CredentialView.swift */; }; 38FF9DC7292E0B160027C09E /* LoggingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FF9DC6292E0B160027C09E /* LoggingView.swift */; }; - 9BE0F5E78A6A916FF7E09A96 /* Pods_NevisExampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NevisExampleApp.release.xcconfig"; path = "Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp.release.xcconfig"; sourceTree = ""; }; + 06000BAB2C37DDDD00AB53A1 /* PasswordAuthenticatorProtectionStatus+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasswordAuthenticatorProtectionStatus+Extension.swift"; sourceTree = ""; }; + 06000BAD2C37ECD800AB53A1 /* PasswordChangerImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordChangerImpl.swift; sourceTree = ""; }; + 06000BAF2C37EDA100AB53A1 /* PasswordEnroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordEnroller.swift; sourceTree = ""; }; + 06000BB12C37EE6A00AB53A1 /* PasswordUserVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordUserVerifier.swift; sourceTree = ""; }; + 06471B142C29685C006879F9 /* AuthenticatorAaid+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AuthenticatorAaid+Extensions.swift"; sourceTree = ""; }; + 068E9ABB2C2D60BA0059E33C /* AuthenticatorValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatorValidator.swift; sourceTree = ""; }; 382FE1D62BADD5FC00999625 /* ConfirmationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationPresenter.swift; sourceTree = ""; }; 382FE1D82BADD60400999625 /* ConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationScreen.swift; sourceTree = ""; }; 3833EB192685EA03002C5E0C /* NevisExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NevisExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -139,8 +148,7 @@ 3859EAF3292CF8A800013472 /* ErrorHandlerChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHandlerChain.swift; sourceTree = ""; }; 3859EAF4292CF8A800013472 /* ErrorHandlerChainImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHandlerChainImpl.swift; sourceTree = ""; }; 3859EAFC292CF92000013472 /* PinUserVerifierImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinUserVerifierImpl.swift; sourceTree = ""; }; - 3859EAFD292CF92000013472 /* RegistrationAuthenticatorSelectorImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationAuthenticatorSelectorImpl.swift; sourceTree = ""; }; - 3859EAFE292CF92000013472 /* AuthenticationAuthenticatorSelectorImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationAuthenticatorSelectorImpl.swift; sourceTree = ""; }; + 3859EAFE292CF92000013472 /* AuthenticatorSelectorImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatorSelectorImpl.swift; sourceTree = ""; }; 3859EAFF292CF92000013472 /* PinChangerImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinChangerImpl.swift; sourceTree = ""; }; 3859EB00292CF92000013472 /* OutOfBandOperationHandlerImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfBandOperationHandlerImpl.swift; sourceTree = ""; }; 3859EB01292CF92000013472 /* AccountSelectorImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountSelectorImpl.swift; sourceTree = ""; }; @@ -149,9 +157,9 @@ 3859EB04292CF92000013472 /* BiometricUserVerifierImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricUserVerifierImpl.swift; sourceTree = ""; }; 3859EB07292CF92000013472 /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; 3859EB08292CF92000013472 /* HomePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePresenter.swift; sourceTree = ""; }; - 3859EB0A292CF92000013472 /* PinPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinPresenter.swift; sourceTree = ""; }; - 3859EB0B292CF92000013472 /* PinScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinScreen.swift; sourceTree = ""; }; - 3859EB0D292CF92000013472 /* PinProtectionInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinProtectionInformation.swift; sourceTree = ""; }; + 3859EB0A292CF92000013472 /* CredentialPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialPresenter.swift; sourceTree = ""; }; + 3859EB0B292CF92000013472 /* CredentialScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialScreen.swift; sourceTree = ""; }; + 3859EB0D292CF92000013472 /* CredentialProtectionInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialProtectionInformation.swift; sourceTree = ""; }; 3859EB0F292CF92000013472 /* ResultPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultPresenter.swift; sourceTree = ""; }; 3859EB10292CF92000013472 /* ResultScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultScreen.swift; sourceTree = ""; }; 3859EB12292CF92000013472 /* ChangeDeviceInformationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeDeviceInformationScreen.swift; sourceTree = ""; }; @@ -220,10 +228,8 @@ 38DABD6929D585C0008B8116 /* LoginValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginValidator.swift; sourceTree = ""; }; 38E17EAF2A28DD7200441821 /* Authenticator+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Authenticator+Extension.swift"; sourceTree = ""; }; 38E1CBF22A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicePasscodeUserVerifierImpl.swift; sourceTree = ""; }; - 38FF9DC4292E09E70027C09E /* PinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinView.swift; sourceTree = ""; }; + 38FF9DC4292E09E70027C09E /* CredentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialView.swift; sourceTree = ""; }; 38FF9DC6292E0B160027C09E /* LoggingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingView.swift; sourceTree = ""; }; - 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NevisExampleApp.debug.xcconfig"; path = "Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp.debug.xcconfig"; sourceTree = ""; }; - F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NevisExampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -231,13 +237,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9BE0F5E78A6A916FF7E09A96 /* Pods_NevisExampleApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 06000BA92C37D9D000AB53A1 /* Pin */ = { + isa = PBXGroup; + children = ( + 3859EAFF292CF92000013472 /* PinChangerImpl.swift */, + 3859EB02292CF92000013472 /* PinEnrollerImpl.swift */, + 3859EAFC292CF92000013472 /* PinUserVerifierImpl.swift */, + ); + path = Pin; + sourceTree = ""; + }; + 06000BAA2C37D9E200AB53A1 /* Password */ = { + isa = PBXGroup; + children = ( + 06000BAD2C37ECD800AB53A1 /* PasswordChangerImpl.swift */, + 06000BAF2C37EDA100AB53A1 /* PasswordEnroller.swift */, + 06000BB12C37EE6A00AB53A1 /* PasswordUserVerifier.swift */, + ); + path = Password; + sourceTree = ""; + }; 382FE1D52BADD5EA00999625 /* Confirmation */ = { isa = PBXGroup; children = ( @@ -252,8 +277,6 @@ children = ( 3833EB1B2685EA03002C5E0C /* NevisExampleApp */, 3833EB1A2685EA03002C5E0C /* Products */, - A651FDC4F5C71ADD993EA999 /* Pods */, - 8E9E59B3AFC9FF84E096D1EB /* Frameworks */, ); sourceTree = ""; }; @@ -362,16 +385,14 @@ 3859EAFB292CF92000013472 /* Interaction */ = { isa = PBXGroup; children = ( + 06000BAA2C37D9E200AB53A1 /* Password */, + 06000BA92C37D9D000AB53A1 /* Pin */, 3859EB01292CF92000013472 /* AccountSelectorImpl.swift */, - 3859EAFE292CF92000013472 /* AuthenticationAuthenticatorSelectorImpl.swift */, + 3859EAFE292CF92000013472 /* AuthenticatorSelectorImpl.swift */, 3859EB04292CF92000013472 /* BiometricUserVerifierImpl.swift */, 38E1CBF22A8E31DE00D4F7EB /* DevicePasscodeUserVerifierImpl.swift */, 3859EB03292CF92000013472 /* OutOfBandOperationHandler.swift */, 3859EB00292CF92000013472 /* OutOfBandOperationHandlerImpl.swift */, - 3859EAFF292CF92000013472 /* PinChangerImpl.swift */, - 3859EB02292CF92000013472 /* PinEnrollerImpl.swift */, - 3859EAFC292CF92000013472 /* PinUserVerifierImpl.swift */, - 3859EAFD292CF92000013472 /* RegistrationAuthenticatorSelectorImpl.swift */, ); path = Interaction; sourceTree = ""; @@ -383,17 +404,17 @@ 3859EB2E292CF92000013472 /* Base */, 3859EB11292CF92000013472 /* Change Device Information */, 382FE1D52BADD5EA00999625 /* Confirmation */, + 3859EB09292CF92000013472 /* Credential */, 3859EB06292CF92000013472 /* Home */, 3859EB14292CF92000013472 /* Launch */, 3859EB26292CF92000013472 /* Logging */, - 3859EB09292CF92000013472 /* Pin */, 3859EB20292CF92000013472 /* Qr Scanner */, 3859EB0E292CF92000013472 /* Result */, + 3859EB1F292CF92000013472 /* Screen.swift */, 3859EB1A292CF92000013472 /* Select Account */, 3859EB29292CF92000013472 /* Select Authenticator */, 3859EB33292CF92000013472 /* Transaction Confirmation */, 38DABD5E29D5817E008B8116 /* Username Password Login */, - 3859EB1F292CF92000013472 /* Screen.swift */, ); path = Screens; sourceTree = ""; @@ -407,21 +428,21 @@ path = Home; sourceTree = ""; }; - 3859EB09292CF92000013472 /* Pin */ = { + 3859EB09292CF92000013472 /* Credential */ = { isa = PBXGroup; children = ( 3859EB0C292CF92000013472 /* Model */, - 3859EB0A292CF92000013472 /* PinPresenter.swift */, - 3859EB0B292CF92000013472 /* PinScreen.swift */, - 38FF9DC4292E09E70027C09E /* PinView.swift */, + 3859EB0A292CF92000013472 /* CredentialPresenter.swift */, + 3859EB0B292CF92000013472 /* CredentialScreen.swift */, + 38FF9DC4292E09E70027C09E /* CredentialView.swift */, ); - path = Pin; + path = Credential; sourceTree = ""; }; 3859EB0C292CF92000013472 /* Model */ = { isa = PBXGroup; children = ( - 3859EB0D292CF92000013472 /* PinProtectionInformation.swift */, + 3859EB0D292CF92000013472 /* CredentialProtectionInformation.swift */, ); path = Model; sourceTree = ""; @@ -486,7 +507,6 @@ 3859EB29292CF92000013472 /* Select Authenticator */ = { isa = PBXGroup; children = ( - 3872BFCF2A29D1930041C594 /* Model */, 3859EB2D292CF92000013472 /* AuthenticatorCell.swift */, 3859EB2C292CF92000013472 /* SelectAuthenticatorItemViewModel.swift */, 3859EB2B292CF92000013472 /* SelectAuthenticatorPresenter.swift */, @@ -571,9 +591,11 @@ isa = PBXGroup; children = ( 38E17EAF2A28DD7200441821 /* Authenticator+Extension.swift */, + 06471B142C29685C006879F9 /* AuthenticatorAaid+Extensions.swift */, 3859EB84292CFA1900013472 /* Dictionary+Extension.swift */, 3859EB83292CFA1900013472 /* Notification+Extension.swift */, 3859EBBA292CFA7000013472 /* OSLog+Extension.swift */, + 06000BAB2C37DDDD00AB53A1 /* PasswordAuthenticatorProtectionStatus+Extension.swift */, 3859EB82292CFA1900013472 /* PinAuthenticatorProtectionStatus+Extension.swift */, 3859EBB8292CFA7000013472 /* String+Extension.swift */, 3859EB7C292CFA1900013472 /* UIApplication+Extension.swift */, @@ -619,6 +641,7 @@ children = ( 3859EB91292CFA1900013472 /* AccountValidator.swift */, 3859EB92292CFA1900013472 /* AuthCloudApiRegistrationValidator.swift */, + 068E9ABB2C2D60BA0059E33C /* AuthenticatorValidator.swift */, 38DABD6929D585C0008B8116 /* LoginValidator.swift */, 3859EB93292CFA1900013472 /* ValidationResult.swift */, ); @@ -645,23 +668,16 @@ 3867C19C2685F55400A8B98B /* Resources */ = { isa = PBXGroup; children = ( - 3833EB2A2685EA03002C5E0C /* Info.plist */, 3833EB272685EA03002C5E0C /* LaunchScreen.storyboard */, - 3833EB252685EA03002C5E0C /* Assets.xcassets */, 38C1B37B269D815A00ED5782 /* Localizable.strings */, + 3833EB252685EA03002C5E0C /* Assets.xcassets */, 38821E6F292502670009522A /* ConfigAuthenticationCloud.plist */, 38821E6D292500ED0009522A /* ConfigIdentitySuite.plist */, + 3833EB2A2685EA03002C5E0C /* Info.plist */, ); path = Resources; sourceTree = ""; }; - 3872BFCF2A29D1930041C594 /* Model */ = { - isa = PBXGroup; - children = ( - ); - path = Model; - sourceTree = ""; - }; 38DABD5E29D5817E008B8116 /* Username Password Login */ = { isa = PBXGroup; children = ( @@ -671,21 +687,10 @@ path = "Username Password Login"; sourceTree = ""; }; - 8E9E59B3AFC9FF84E096D1EB /* Frameworks */ = { - isa = PBXGroup; - children = ( - F141C519C68C029C049F2F79 /* Pods_NevisExampleApp.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; A651FDC4F5C71ADD993EA999 /* Pods */ = { isa = PBXGroup; children = ( - 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */, - 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -696,12 +701,10 @@ isa = PBXNativeTarget; buildConfigurationList = 3833EB2D2685EA03002C5E0C /* Build configuration list for PBXNativeTarget "NevisExampleApp" */; buildPhases = ( - 79CB52AC7DDC23DF75949849 /* [CP] Check Pods Manifest.lock */, 3867C1972685EE1F00A8B98B /* Swiftformat */, 3833EB152685EA03002C5E0C /* Sources */, 3833EB162685EA03002C5E0C /* Frameworks */, 3833EB172685EA03002C5E0C /* Resources */, - B33D1E141A1B58EE4EBF5ABB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -718,8 +721,9 @@ 3833EB112685EA03002C5E0C /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1250; + LastUpgradeCheck = 1540; TargetAttributes = { 3833EB182685EA03002C5E0C = { CreatedOnToolsVersion = 12.5; @@ -780,45 +784,6 @@ shellPath = /bin/sh; shellScript = "if which swiftformat >/dev/null; then\n echo \"SwiftFormat version: $(swiftformat --version)\"\n swiftformat \"${SRCROOT}/\"\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n"; }; - 79CB52AC7DDC23DF75949849 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NevisExampleApp-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - B33D1E141A1B58EE4EBF5ABB /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NevisExampleApp/Pods-NevisExampleApp-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -851,12 +816,14 @@ 3859EB4E292CF92000013472 /* SelectAccountScreen.swift in Sources */, 3859EB3A292CF92000013472 /* OutOfBandOperationHandlerImpl.swift in Sources */, 3859EBBC292CFA7000013472 /* UIDevice+Extension.swift in Sources */, + 06471B152C29685C006879F9 /* AuthenticatorAaid+Extensions.swift in Sources */, 3859EB40292CF92000013472 /* HomePresenter.swift in Sources */, 3859EAEB292CF82D00013472 /* LoginConfiguration.swift in Sources */, 3859EB3C292CF92000013472 /* PinEnrollerImpl.swift in Sources */, 3859EBB2292CFA1900013472 /* LoginService.swift in Sources */, + 06000BB22C37EE6A00AB53A1 /* PasswordUserVerifier.swift in Sources */, 3859EBA7292CFA1900013472 /* UIStackView+Extension.swift in Sources */, - 3859EB42292CF92000013472 /* PinScreen.swift in Sources */, + 3859EB42292CF92000013472 /* CredentialScreen.swift in Sources */, 3859EB65292CF9AF00013472 /* OperationError.swift in Sources */, 3859EAE9292CF82D00013472 /* AppConfiguration.swift in Sources */, 3859EBB7292CFA1900013472 /* Strings.swift in Sources */, @@ -867,10 +834,9 @@ 3859EADA292CF78900013472 /* Coordinator.swift in Sources */, 3859EAF5292CF8A800013472 /* ErrorHandler.swift in Sources */, 3859EB4C292CF92000013472 /* SelectAccountItemViewModel.swift in Sources */, - 3859EB41292CF92000013472 /* PinPresenter.swift in Sources */, + 3859EB41292CF92000013472 /* CredentialPresenter.swift in Sources */, 3859EAD7292CF78900013472 /* AppCoordinatorImpl.swift in Sources */, 3859EAF6292CF8A800013472 /* NevisErrorHandler.swift in Sources */, - 3859EB37292CF92000013472 /* RegistrationAuthenticatorSelectorImpl.swift in Sources */, 3859EB59292CF92000013472 /* SelectAuthenticatorItemViewModel.swift in Sources */, 3859EB3D292CF92000013472 /* OutOfBandOperationHandler.swift in Sources */, 3859EBAD292CFA1900013472 /* ClientProviderImpl.swift in Sources */, @@ -886,7 +852,7 @@ 3859EB45292CF92000013472 /* ResultScreen.swift in Sources */, 3859EADF292CF7F000013472 /* AppAssembly.swift in Sources */, 3859EAE7292CF82D00013472 /* ConfigurationLoader.swift in Sources */, - 3859EB43292CF92000013472 /* PinProtectionInformation.swift in Sources */, + 3859EB43292CF92000013472 /* CredentialProtectionInformation.swift in Sources */, 38DABD6529D581D3008B8116 /* UsernamePasswordLoginScreen.swift in Sources */, 3859EAD9292CF78900013472 /* NavigationParameterizable.swift in Sources */, 3859EBAA292CFA1900013472 /* Dictionary+Extension.swift in Sources */, @@ -897,7 +863,8 @@ 3859EBBD292CFA7000013472 /* OSLog+Extension.swift in Sources */, 3859EB46292CF92000013472 /* ChangeDeviceInformationScreen.swift in Sources */, 3859EB99292CFA1900013472 /* Style.swift in Sources */, - 38FF9DC5292E09E70027C09E /* PinView.swift in Sources */, + 06000BB02C37EDA100AB53A1 /* PasswordEnroller.swift in Sources */, + 38FF9DC5292E09E70027C09E /* CredentialView.swift in Sources */, 3859EAD8292CF78900013472 /* AppCoordinator.swift in Sources */, 3859EBAE292CFA1900013472 /* ClientProvider.swift in Sources */, 3859EBB4292CFA1900013472 /* AuthCloudApiRegistrationValidator.swift in Sources */, @@ -908,16 +875,19 @@ 382FE1D72BADD5FC00999625 /* ConfirmationPresenter.swift in Sources */, 38DABD6A29D585C0008B8116 /* LoginValidator.swift in Sources */, 3859EBB5292CFA1900013472 /* ValidationResult.swift in Sources */, - 3859EB38292CF92000013472 /* AuthenticationAuthenticatorSelectorImpl.swift in Sources */, + 3859EB38292CF92000013472 /* AuthenticatorSelectorImpl.swift in Sources */, + 068E9ABC2C2D60BA0059E33C /* AuthenticatorValidator.swift in Sources */, 3859EB47292CF92000013472 /* ChangeDeviceInformationPresenter.swift in Sources */, 3859EB5B292CF92000013472 /* BaseScreen.swift in Sources */, 38DABD6629D581D3008B8116 /* UsernamePasswordLoginPresenter.swift in Sources */, + 06000BAE2C37ECD800AB53A1 /* PasswordChangerImpl.swift in Sources */, 3859EB39292CF92000013472 /* PinChangerImpl.swift in Sources */, 3859EB5D292CF92000013472 /* AuthCloudApiRegistrationScreen.swift in Sources */, 3859EB3E292CF92000013472 /* BiometricUserVerifierImpl.swift in Sources */, 3859EB6A292CF9D700013472 /* DeepLink.swift in Sources */, 3859EB5C292CF92000013472 /* AuthCloudApiRegistrationPresenter.swift in Sources */, 3859EB5E292CF92000013472 /* TransactionConfirmationPresenter.swift in Sources */, + 06000BAC2C37DDDD00AB53A1 /* PasswordAuthenticatorProtectionStatus+Extension.swift in Sources */, 38FF9DC7292E0B160027C09E /* LoggingView.swift in Sources */, 38E17EB02A28DD7200441821 /* Authenticator+Extension.swift in Sources */, 3859EBB1292CFA1900013472 /* LoginResponse.swift in Sources */, @@ -1071,7 +1041,6 @@ }; 3833EB2E2685EA03002C5E0C /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 67BBC52FF1A43798284EED29 /* Pods-NevisExampleApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1103,7 +1072,6 @@ }; 3833EB2F2685EA03002C5E0C /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0B956422D44439C093E00399 /* Pods-NevisExampleApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/NevisExampleApp.xcodeproj/xcshareddata/xcschemes/NevisExampleApp.xcscheme b/NevisExampleApp.xcodeproj/xcshareddata/xcschemes/NevisExampleApp.xcscheme index 6936157..4c2185e 100644 --- a/NevisExampleApp.xcodeproj/xcshareddata/xcschemes/NevisExampleApp.xcscheme +++ b/NevisExampleApp.xcodeproj/xcshareddata/xcschemes/NevisExampleApp.xcscheme @@ -1,6 +1,6 @@ (PinPresenter.self, argument: arg)) + container.register(CredentialScreen.self) { (res: Resolver, arg: NavigationParameterizable) in + CredentialScreen(presenter: res ~> (CredentialPresenter.self, argument: arg)) }.inObjectScope(.weak) container.register(TransactionConfirmationScreen.self) { (res: Resolver, arg: NavigationParameterizable) in @@ -90,6 +92,8 @@ private extension AppAssembly { .inObjectScope(.container) } + // MARK: Coordinators + /// Registers the coordinators. /// /// - Parameter container: The container provided by the `Assembler`. @@ -99,6 +103,8 @@ private extension AppAssembly { .inObjectScope(.container) } + // MARK: Presenters + /// Registers the presenters. /// /// - Parameter container: The container provided by the `Assembler`. @@ -126,6 +132,7 @@ private extension AppAssembly { return AuthCloudApiRegistrationPresenter(clientProvider: res~>, authenticatorSelector: authenticatorSelector, pinEnroller: res~>, + passwordEnroller: res~>, biometricUserVerifier: res~>, devicePasscodeUserVerifier: res~>, appCoordinator: res~>, @@ -141,6 +148,7 @@ private extension AppAssembly { clientProvider: res~>, authenticatorSelector: authenticatorSelector, pinEnroller: res~>, + passwordEnroller: res~>, biometricUserVerifier: res~>, devicePasscodeUserVerifier: res~>, appCoordinator: res~>, @@ -155,6 +163,8 @@ private extension AppAssembly { authenticatorSelector: authenticatorSelector, pinChanger: res~>, pinUserVerifier: res~>, + passwordChanger: res~>, + passwordUserVerifier: res~>, biometricUserVerifier: res~>, devicePasscodeUserVerifier: res~>, appCoordinator: res~>, @@ -168,9 +178,9 @@ private extension AppAssembly { initializer: SelectAuthenticatorPresenter.init) .inObjectScope(.transient) - container.autoregister(PinPresenter.self, + container.autoregister(CredentialPresenter.self, argument: NavigationParameterizable.self, - initializer: PinPresenter.init) + initializer: CredentialPresenter.init) .inObjectScope(.transient) container.autoregister(TransactionConfirmationPresenter.self, @@ -193,6 +203,8 @@ private extension AppAssembly { .inObjectScope(.transient) } + // MARK: Components + /// Registers the components. /// /// - Parameter container: The container provided by the `Assembler`. @@ -208,13 +220,21 @@ private extension AppAssembly { container.autoregister(AccountSelector.self, initializer: AccountSelectorImpl.init) - container.autoregister(AuthenticatorSelector.self, - name: AuthenticationAuthenticatorSelectorName, - initializer: AuthenticationAuthenticatorSelectorImpl.init) + container.register(AuthenticatorSelector.self, + name: RegistrationAuthenticatorSelectorName) { res in + AuthenticatorSelectorImpl(appCoordinator: res~>, + logger: res~>, + configurationLoader: res~>, + operation: .registration) + } - container.autoregister(AuthenticatorSelector.self, - name: RegistrationAuthenticatorSelectorName, - initializer: RegistrationAuthenticatorSelectorImpl.init) + container.register(AuthenticatorSelector.self, + name: AuthenticationAuthenticatorSelectorName) { res in + AuthenticatorSelectorImpl(appCoordinator: res~>, + logger: res~>, + configurationLoader: res~>, + operation: .authentication) + } container.autoregister(PinEnroller.self, initializer: PinEnrollerImpl.init) @@ -225,6 +245,15 @@ private extension AppAssembly { container.autoregister(PinUserVerifier.self, initializer: PinUserVerifierImpl.init) + container.autoregister(PasswordEnroller.self, + initializer: PasswordEnrollerImpl.init) + + container.autoregister(PasswordChanger.self, + initializer: PasswordChangerImpl.init) + + container.autoregister(PasswordUserVerifier.self, + initializer: PasswordUserVerifierImpl.init) + container.autoregister(BiometricUserVerifier.self, initializer: BiometricUserVerifierImpl.init) @@ -249,6 +278,8 @@ private extension AppAssembly { authenticationAuthenticatorSelector: authSelectorForAuth, pinEnroller: res~>, pinUserVerifier: res~>, + passwordEnroller: res~>, + passwordUserVerifier: res~>, biometricUserVerifier: res~>, devicePasscodeUserVerifier: res~>, appCoordinator: res~>, diff --git a/NevisExampleApp/Error/AppError.swift b/NevisExampleApp/Error/AppError.swift index 2309dc2..3ce2b4e 100644 --- a/NevisExampleApp/Error/AppError.swift +++ b/NevisExampleApp/Error/AppError.swift @@ -20,6 +20,8 @@ enum AppError: Error { case authenticatorNotFound /// No PIN authenticator found in the list of registered authenticators. case pinAuthenticatorNotFound + /// No Password authenticator found in the list of registered authenticators. + case passwordAuthenticatorNotFound /// No device information found. case deviceInformationNotFound /// No data found during Auth Cloud registration. @@ -49,6 +51,8 @@ extension AppError: LocalizedError { L10n.Error.App.authenticatorNotFound case .pinAuthenticatorNotFound: L10n.Error.App.pinAuthenticatorNotFound + case .passwordAuthenticatorNotFound: + L10n.Error.App.passwordAuthenticatorNotFound case .deviceInformationNotFound: L10n.Error.App.deviceInformationNotFound case .authCloudApiRegistrationDataNotFound: diff --git a/NevisExampleApp/Interaction/AccountSelectorImpl.swift b/NevisExampleApp/Interaction/AccountSelectorImpl.swift index 5ad615f..d5726a0 100644 --- a/NevisExampleApp/Interaction/AccountSelectorImpl.swift +++ b/NevisExampleApp/Interaction/AccountSelectorImpl.swift @@ -20,9 +20,6 @@ class AccountSelectorImpl { /// The application coordinator. private let appCoordinator: AppCoordinator - /// The error handler chain. - private let errorHandlerChain: ErrorHandlerChain - /// The logger. private let logger: SDKLogger @@ -32,13 +29,11 @@ class AccountSelectorImpl { /// /// - Parameters: /// - appCoordinator: The application coordinator. - /// - errorHandlerChain: The error handler chain. /// - logger: The logger. init(appCoordinator: AppCoordinator, errorHandlerChain: ErrorHandlerChain, logger: SDKLogger) { self.appCoordinator = appCoordinator - self.errorHandlerChain = errorHandlerChain self.logger = logger } } @@ -87,7 +82,7 @@ extension AccountSelectorImpl: AccountSelector { appCoordinator.navigateToAccountSelection(with: parameter) } case let .failure(error): - errorHandlerChain.handle(error: error) + handler.cancel() } } } diff --git a/NevisExampleApp/Interaction/AuthenticationAuthenticatorSelectorImpl.swift b/NevisExampleApp/Interaction/AuthenticationAuthenticatorSelectorImpl.swift deleted file mode 100644 index 37a3a13..0000000 --- a/NevisExampleApp/Interaction/AuthenticationAuthenticatorSelectorImpl.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Nevis Mobile Authentication SDK Example App -// -// Copyright © 2022. Nevis Security AG. All rights reserved. -// - -import NevisMobileAuthentication - -/// The unique name of authenticator selector implementation for Authentication operation. -let AuthenticationAuthenticatorSelectorName = "auth_selector_auth" - -/// Default implementation of ``AuthenticatorSelector`` protocol used for authentication. -/// -/// Navigates to the ``SelectAuthenticatorScreen`` where the user can select from the available authenticators. -class AuthenticationAuthenticatorSelectorImpl { - - // MARK: - Properties - - /// The application coordinator. - private let appCoordinator: AppCoordinator - - /// The logger. - private let logger: SDKLogger - - // MARK: - Initialization - - /// Creates a new instance. - /// - /// - Parameters: - /// - appCoordinator: The application coordinator. - /// - logger: The logger. - init(appCoordinator: AppCoordinator, - logger: SDKLogger) { - self.appCoordinator = appCoordinator - self.logger = logger - } -} - -// MARK: - AuthenticatorSelector - -extension AuthenticationAuthenticatorSelectorImpl: AuthenticatorSelector { - func selectAuthenticator(context: AuthenticatorSelectionContext, handler: AuthenticatorSelectionHandler) { - logger.log("Please select one of the received available authenticators!") - let authenticatorItems = context.authenticators.filter { - guard let registration = $0.registration else { - return false - } - - // Do not display: - // - policy non-registered authenticators - // - not hardware supported authenticators. - return $0.isSupportedByHardware && registration.isRegistered(context.account.username) - }.map { - AuthenticatorItem(authenticator: $0, - isPolicyCompliant: context.isPolicyCompliant(authenticatorAaid: $0.aaid), - isUserEnrolled: $0.isEnrolled(username: context.account.username)) - } - - let parameter: SelectAuthenticatorParameter = .select(authenticatorItems: authenticatorItems, - handler: handler) - appCoordinator.navigateToAuthenticatorSelection(with: parameter) - } -} diff --git a/NevisExampleApp/Interaction/AuthenticatorSelectorImpl.swift b/NevisExampleApp/Interaction/AuthenticatorSelectorImpl.swift new file mode 100644 index 0000000..e0dad94 --- /dev/null +++ b/NevisExampleApp/Interaction/AuthenticatorSelectorImpl.swift @@ -0,0 +1,93 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2022. Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// The unique name of authenticator selector implementation for Registration operation. +let RegistrationAuthenticatorSelectorName = "auth_selector_reg" + +/// The unique name of authenticator selector implementation for Authentication operation. +let AuthenticationAuthenticatorSelectorName = "auth_selector_auth" + +/// Default implementation of ``AuthenticatorSelector`` protocol. +/// +/// Navigates to the ``SelectAuthenticatorScreen`` where the user can select from the available authenticators. +class AuthenticatorSelectorImpl { + + enum Operation { + case registration + case authentication + } + + // MARK: - Properties + + /// The application coordinator. + private let appCoordinator: AppCoordinator + + /// The logger. + private let logger: SDKLogger + + /// The configuration loader. + private let configurationLoader: ConfigurationLoader + + /// The current operation. + private let operation: Operation + + // MARK: - Initialization + + /// Creates a new instance. + /// + /// - Parameters: + /// - appCoordinator: The application coordinator. + /// - logger: The logger. + /// - configurationLoader: The configuration loader. + /// - operation: The current operation. + init(appCoordinator: AppCoordinator, + logger: SDKLogger, + configurationLoader: ConfigurationLoader, + operation: Operation) { + self.appCoordinator = appCoordinator + self.logger = logger + self.configurationLoader = configurationLoader + self.operation = operation + } +} + +// MARK: - AuthenticatorSelector + +extension AuthenticatorSelectorImpl: AuthenticatorSelector { + func selectAuthenticator(context: AuthenticatorSelectionContext, handler: AuthenticatorSelectionHandler) { + guard let configuration = try? configurationLoader.load() else { + logger.log("Configuration cannot be loaded during authenticator selection!", color: .red) + return handler.cancel() + } + + logger.log("Please select one of the received available authenticators!") + + let validator = AuthenticatorValidator() + let validationResult = switch operation { + case .registration: validator.validateForRegistration(context: context, whitelistedAuthenticators: configuration.authenticatorWhitelist) + case .authentication: validator.validateForAuthentication(context: context, whitelistedAuthenticators: configuration.authenticatorWhitelist) + } + + switch validationResult { + case let .success(validatedAuthenticators): + let authenticatorItems = validatedAuthenticators.map { + AuthenticatorItem(authenticator: $0, + isPolicyCompliant: context.isPolicyCompliant(authenticatorAaid: $0.aaid), + isUserEnrolled: $0.isEnrolled(username: context.account.username)) + } + let parameter: SelectAuthenticatorParameter = .select(authenticatorItems: authenticatorItems, + handler: handler) + if case .registration = operation { + appCoordinator.topScreen?.enableInteraction() + } + appCoordinator.navigateToAuthenticatorSelection(with: parameter) + case .failure: + handler.cancel() + } + } +} diff --git a/NevisExampleApp/Interaction/OutOfBandOperationHandlerImpl.swift b/NevisExampleApp/Interaction/OutOfBandOperationHandlerImpl.swift index 1a20b12..01612d6 100644 --- a/NevisExampleApp/Interaction/OutOfBandOperationHandlerImpl.swift +++ b/NevisExampleApp/Interaction/OutOfBandOperationHandlerImpl.swift @@ -29,6 +29,12 @@ final class OutOfBandOperationHandlerImpl { /// The PIN user verifier. private let pinUserVerifier: PinUserVerifier + /// The Password enroller. + private let passwordEnroller: PasswordEnroller + + /// The Password user verifier. + private let passwordUserVerifier: PasswordUserVerifier + /// The biometric user verifier. private let biometricUserVerifier: BiometricUserVerifier @@ -60,6 +66,8 @@ final class OutOfBandOperationHandlerImpl { /// - authenticationAuthenticatorSelector: The authenticator selector used during authentication. /// - pinEnroller: The PIN enroller. /// - pinUserVerifier: The PIN user verifier. + /// - passwordEnroller: The Password enroller. + /// - passwordUserVerifier: The Password user verifier. /// - biometricUserVerifier: The biometric user verifier. /// - devicePasscodeUserVerifier: The device passcode user verifier. /// - appCoordinator: The application coordinator. @@ -71,6 +79,8 @@ final class OutOfBandOperationHandlerImpl { authenticationAuthenticatorSelector: AuthenticatorSelector, pinEnroller: PinEnroller, pinUserVerifier: PinUserVerifier, + passwordEnroller: PasswordEnroller, + passwordUserVerifier: PasswordUserVerifier, biometricUserVerifier: BiometricUserVerifier, devicePasscodeUserVerifier: DevicePasscodeUserVerifier, appCoordinator: AppCoordinator, @@ -82,6 +92,8 @@ final class OutOfBandOperationHandlerImpl { self.authenticationAuthenticatorSelector = authenticationAuthenticatorSelector self.pinEnroller = pinEnroller self.pinUserVerifier = pinUserVerifier + self.passwordEnroller = passwordEnroller + self.passwordUserVerifier = passwordUserVerifier self.biometricUserVerifier = biometricUserVerifier self.devicePasscodeUserVerifier = devicePasscodeUserVerifier self.appCoordinator = appCoordinator @@ -155,6 +167,7 @@ private extension OutOfBandOperationHandlerImpl { .deviceInformation(deviceInformation) .authenticatorSelector(registrationAuthenticatorSelector) .pinEnroller(pinEnroller) + .passwordEnroller(passwordEnroller) .biometricUserVerifier(biometricUserVerifier) .devicePasscodeUserVerifier(devicePasscodeUserVerifier) .onSuccess { @@ -177,6 +190,7 @@ private extension OutOfBandOperationHandlerImpl { .accountSelector(accountSelector) .authenticatorSelector(authenticationAuthenticatorSelector) .pinUserVerifier(pinUserVerifier) + .passwordUserVerifier(passwordUserVerifier) .biometricUserVerifier(biometricUserVerifier) .devicePasscodeUserVerifier(devicePasscodeUserVerifier) .onSuccess { _ in diff --git a/NevisExampleApp/Interaction/Password/PasswordChangerImpl.swift b/NevisExampleApp/Interaction/Password/PasswordChangerImpl.swift new file mode 100644 index 0000000..b3258fd --- /dev/null +++ b/NevisExampleApp/Interaction/Password/PasswordChangerImpl.swift @@ -0,0 +1,58 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024 Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// Default implementation of ``PasswordChanger`` protocol. +/// +/// Navigates to the ``CredentialScreen`` where the user can change the Password. +class PasswordChangerImpl { + + // MARK: - Properties + + /// The application coordinator. + private let appCoordinator: AppCoordinator + + /// The logger. + private let logger: SDKLogger + + // MARK: - Initialization + + /// Creates a new instance. + /// + /// - Parameters: + /// - appCoordinator: The application coordinator. + /// - logger: The logger. + init(appCoordinator: AppCoordinator, + logger: SDKLogger) { + self.appCoordinator = appCoordinator + self.logger = logger + } +} + +// MARK: - PasswordChanger + +extension PasswordChangerImpl: PasswordChanger { + func changePassword(context: PasswordChangeContext, handler: PasswordChangeHandler) { + if context.lastRecoverableError != nil { + logger.log("Password change failed. Please try again.") + } + else { + logger.log("Please start Password change.") + } + + appCoordinator.topScreen?.enableInteraction() + let parameter: PasswordParameter = .credentialChange(protectionStatus: context.authenticatorProtectionStatus, + lastRecoverableError: context.lastRecoverableError, + handler: handler) + appCoordinator.navigateToCredential(with: parameter) + } + + /// You can add custom Password policy by overriding the `passwordPolicy` getter. +// func passwordPolicy() -> PasswordPolicy { +// // custom PasswordPolicy implementation +// } +} diff --git a/NevisExampleApp/Interaction/Password/PasswordEnroller.swift b/NevisExampleApp/Interaction/Password/PasswordEnroller.swift new file mode 100644 index 0000000..1bfcc9e --- /dev/null +++ b/NevisExampleApp/Interaction/Password/PasswordEnroller.swift @@ -0,0 +1,56 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024 Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// Default implementation of ``PasswordEnroller`` protocol. +/// +/// Navigates to the ``CredentialScreen`` where the user can enroll the Passowrord authenticator. +class PasswordEnrollerImpl { + + // MARK: - Properties + + /// The application coordinator. + private let appCoordinator: AppCoordinator + + /// The logger. + private let logger: SDKLogger + + // MARK: - Initialization + + /// Creates a new instance. + /// + /// - Parameters: + /// - appCoordinator: The application coordinator. + /// - logger: The logger. + init(appCoordinator: AppCoordinator, + logger: SDKLogger) { + self.appCoordinator = appCoordinator + self.logger = logger + } +} + +// MARK: - PasswordEnroller + +extension PasswordEnrollerImpl: PasswordEnroller { + func enrollPassword(context: PasswordEnrollmentContext, handler: PasswordEnrollmentHandler) { + if context.lastRecoverableError != nil { + logger.log("Password enrollment failed. Please try again.") + } + else { + logger.log("Please start Password enrollment.") + } + + let parameter: PasswordParameter = .enrollment(lastRecoverableError: context.lastRecoverableError, + handler: handler) + appCoordinator.navigateToCredential(with: parameter) + } + + /// You can add custom Password policy by overriding the `passwordPolicy` getter. +// func passwordPolicy() -> PasswordPolicy { +// // custom PasswordPolicy implementation +// } +} diff --git a/NevisExampleApp/Interaction/Password/PasswordUserVerifier.swift b/NevisExampleApp/Interaction/Password/PasswordUserVerifier.swift new file mode 100644 index 0000000..948a467 --- /dev/null +++ b/NevisExampleApp/Interaction/Password/PasswordUserVerifier.swift @@ -0,0 +1,52 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024 Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// Default implementation of ``PasswordUserVerifier`` protocol. +/// +/// Navigates to the ``CredentialScreen`` where the user can verify the Password. +class PasswordUserVerifierImpl { + + // MARK: - Properties + + /// The application coordinator. + private let appCoordinator: AppCoordinator + + /// The logger. + private let logger: SDKLogger + + // MARK: - Initialization + + /// Creates a new instance. + /// + /// - Parameters: + /// - appCoordinator: The application coordinator. + /// - logger: The logger. + init(appCoordinator: AppCoordinator, + logger: SDKLogger) { + self.appCoordinator = appCoordinator + self.logger = logger + } +} + +// MARK: - PasswordUserVerifier + +extension PasswordUserVerifierImpl: PasswordUserVerifier { + func verifyPassword(context: PasswordUserVerificationContext, handler: PasswordUserVerificationHandler) { + if context.lastRecoverableError != nil { + logger.log("Password user verification failed. Please try again.") + } + else { + logger.log("Please start Password user verification.") + } + + let parameter: PasswordParameter = .verification(protectionStatus: context.authenticatorProtectionStatus, + lastRecoverableError: context.lastRecoverableError, + handler: handler) + appCoordinator.navigateToCredential(with: parameter) + } +} diff --git a/NevisExampleApp/Interaction/PinChangerImpl.swift b/NevisExampleApp/Interaction/Pin/PinChangerImpl.swift similarity index 92% rename from NevisExampleApp/Interaction/PinChangerImpl.swift rename to NevisExampleApp/Interaction/Pin/PinChangerImpl.swift index 1366647..29724f1 100644 --- a/NevisExampleApp/Interaction/PinChangerImpl.swift +++ b/NevisExampleApp/Interaction/Pin/PinChangerImpl.swift @@ -8,7 +8,7 @@ import NevisMobileAuthentication /// Default implementation of ``PinChanger`` protocol. /// -/// Navigates to the ``PinScreen`` where the user can change the PIN. +/// Navigates to the ``CredentialScreen`` where the user can change the PIN. class PinChangerImpl { // MARK: - Properties @@ -48,7 +48,7 @@ extension PinChangerImpl: PinChanger { let parameter: PinParameter = .credentialChange(protectionStatus: context.authenticatorProtectionStatus, lastRecoverableError: context.lastRecoverableError, handler: handler) - appCoordinator.navigateToPin(with: parameter) + appCoordinator.navigateToCredential(with: parameter) } /// You can add custom PIN policy by overriding the `pinPolicy` getter. diff --git a/NevisExampleApp/Interaction/PinEnrollerImpl.swift b/NevisExampleApp/Interaction/Pin/PinEnrollerImpl.swift similarity index 90% rename from NevisExampleApp/Interaction/PinEnrollerImpl.swift rename to NevisExampleApp/Interaction/Pin/PinEnrollerImpl.swift index 18db9d6..0b7fbf8 100644 --- a/NevisExampleApp/Interaction/PinEnrollerImpl.swift +++ b/NevisExampleApp/Interaction/Pin/PinEnrollerImpl.swift @@ -8,7 +8,7 @@ import NevisMobileAuthentication /// Default implementation of ``PinEnroller`` protocol. /// -/// Navigates to the ``PinScreen`` where the user can enroll the PIN authenticator. +/// Navigates to the ``CredentialScreen`` where the user can enroll the PIN authenticator. class PinEnrollerImpl { // MARK: - Properties @@ -46,7 +46,7 @@ extension PinEnrollerImpl: PinEnroller { let parameter: PinParameter = .enrollment(lastRecoverableError: context.lastRecoverableError, handler: handler) - appCoordinator.navigateToPin(with: parameter) + appCoordinator.navigateToCredential(with: parameter) } /// You can add custom PIN policy by overriding the `pinPolicy` getter. diff --git a/NevisExampleApp/Interaction/PinUserVerifierImpl.swift b/NevisExampleApp/Interaction/Pin/PinUserVerifierImpl.swift similarity index 90% rename from NevisExampleApp/Interaction/PinUserVerifierImpl.swift rename to NevisExampleApp/Interaction/Pin/PinUserVerifierImpl.swift index 7e8bf46..51ce390 100644 --- a/NevisExampleApp/Interaction/PinUserVerifierImpl.swift +++ b/NevisExampleApp/Interaction/Pin/PinUserVerifierImpl.swift @@ -8,7 +8,7 @@ import NevisMobileAuthentication /// Default implementation of ``PinUserVerifier`` protocol. /// -/// Navigates to the ``PinScreen`` where the user can verify the PIN. +/// Navigates to the ``CredentialScreen`` where the user can verify the PIN. class PinUserVerifierImpl { // MARK: - Properties @@ -47,6 +47,6 @@ extension PinUserVerifierImpl: PinUserVerifier { let parameter: PinParameter = .verification(protectionStatus: context.authenticatorProtectionStatus, lastRecoverableError: context.lastRecoverableError, handler: handler) - appCoordinator.navigateToPin(with: parameter) + appCoordinator.navigateToCredential(with: parameter) } } diff --git a/NevisExampleApp/Interaction/RegistrationAuthenticatorSelectorImpl.swift b/NevisExampleApp/Interaction/RegistrationAuthenticatorSelectorImpl.swift deleted file mode 100644 index bc6a652..0000000 --- a/NevisExampleApp/Interaction/RegistrationAuthenticatorSelectorImpl.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Nevis Mobile Authentication SDK Example App -// -// Copyright © 2022. Nevis Security AG. All rights reserved. -// - -import NevisMobileAuthentication - -/// The unique name of authenticator selector implementation for Registration operation. -let RegistrationAuthenticatorSelectorName = "auth_selector_reg" - -/// Default implementation of ``AuthenticatorSelector`` protocol used for registration. -/// -/// Navigates to the ``SelectAuthenticatorScreen`` where the user can select from the available authenticators. -class RegistrationAuthenticatorSelectorImpl { - - // MARK: - Properties - - /// The application coordinator. - private let appCoordinator: AppCoordinator - - /// The logger. - private let logger: SDKLogger - - // MARK: - Initialization - - /// Creates a new instance. - /// - /// - Parameters: - /// - appCoordinator: The application coordinator. - /// - logger: The logger. - init(appCoordinator: AppCoordinator, - logger: SDKLogger) { - self.appCoordinator = appCoordinator - self.logger = logger - } -} - -// MARK: - AuthenticatorSelector - -extension RegistrationAuthenticatorSelectorImpl: AuthenticatorSelector { - func selectAuthenticator(context: AuthenticatorSelectionContext, handler: AuthenticatorSelectionHandler) { - logger.log("Please select one of the received available authenticators!") - let authenticatorItems = context.authenticators.filter { - // Do not display: - // - policy non-compliant authenticators (this includes already registered authenticators) - // - not hardware supported authenticators. - $0.isSupportedByHardware && context.isPolicyCompliant(authenticatorAaid: $0.aaid) - }.map { - AuthenticatorItem(authenticator: $0, - isPolicyCompliant: true, - isUserEnrolled: $0.isEnrolled(username: context.account.username)) - } - - appCoordinator.topScreen?.enableInteraction() - let parameter: SelectAuthenticatorParameter = .select(authenticatorItems: authenticatorItems, - handler: handler) - appCoordinator.navigateToAuthenticatorSelection(with: parameter) - } -} diff --git a/NevisExampleApp/Model/AuthenticatorItem.swift b/NevisExampleApp/Model/AuthenticatorItem.swift index 3d1a835..9e4d01b 100644 --- a/NevisExampleApp/Model/AuthenticatorItem.swift +++ b/NevisExampleApp/Model/AuthenticatorItem.swift @@ -23,6 +23,6 @@ struct AuthenticatorItem { /// Tells that whether this authenticator item is selectable on select authenticator view or not. /// The value is calculated based on the ``AuthenticatorItem/isPolicyCompliant`` and ``AuthenticatorItem/isUserEnrolled`` flags. var isEnabled: Bool { - isPolicyCompliant && (authenticator.aaid == AuthenticatorAaid.Pin.rawValue || isUserEnrolled) + isPolicyCompliant && (authenticator.aaid == AuthenticatorAaid.Pin.rawValue || authenticator.aaid == AuthenticatorAaid.Password.rawValue || isUserEnrolled) } } diff --git a/NevisExampleApp/Model/Operation.swift b/NevisExampleApp/Model/Operation.swift index f940a9d..eb8a0a9 100644 --- a/NevisExampleApp/Model/Operation.swift +++ b/NevisExampleApp/Model/Operation.swift @@ -29,6 +29,9 @@ public enum Operation { /// PIN change operation. case pinChange + /// Password change operation. + case passwordChange + /// Device information change operation. case deviceInformationChange @@ -54,7 +57,9 @@ public enum Operation { case .deregistration: L10n.Operation.Deregistration.title case .pinChange: - L10n.Operation.Pinchange.title + L10n.Operation.PinChange.title + case .passwordChange: + L10n.Operation.PasswordChange.title case .deviceInformationChange: L10n.Operation.DeviceInformationChange.title case .localData: diff --git a/NevisExampleApp/Resources/ConfigAuthenticationCloud.plist b/NevisExampleApp/Resources/ConfigAuthenticationCloud.plist index 307c7f5..f6bfc90 100644 --- a/NevisExampleApp/Resources/ConfigAuthenticationCloud.plist +++ b/NevisExampleApp/Resources/ConfigAuthenticationCloud.plist @@ -12,5 +12,13 @@ hostName myinstance.mauth.nevis.cloud + authenticatorWhitelist + + F1D0#1001 + F1D0#1002 + F1D0#1003 + F1D0#1004 + F1D0#1005 + diff --git a/NevisExampleApp/Resources/ConfigIdentitySuite.plist b/NevisExampleApp/Resources/ConfigIdentitySuite.plist index 1b93c8e..7229c08 100644 --- a/NevisExampleApp/Resources/ConfigIdentitySuite.plist +++ b/NevisExampleApp/Resources/ConfigIdentitySuite.plist @@ -35,5 +35,12 @@ userInteractionTimeoutInSeconds 240 + authenticatorWhitelist + + F1D0#1001 + F1D0#1002 + F1D0#1003 + F1D0#1004 + diff --git a/NevisExampleApp/Resources/en.lproj/Localizable.strings b/NevisExampleApp/Resources/en.lproj/Localizable.strings index 361e9e3..cf35874 100644 --- a/NevisExampleApp/Resources/en.lproj/Localizable.strings +++ b/NevisExampleApp/Resources/en.lproj/Localizable.strings @@ -9,6 +9,7 @@ "home_authenticate_button" = "In-Band Authenticate"; "home_deregister_button" = "Deregister"; "home_pin_change_button" = "PIN change"; +"home_password_change_button" = "Password change"; "home_change_device_information_button" = "Change Device Information"; "home_auth_cloud_api_registration_button" = "Auth Cloud Api Registration"; "home_delete_authenticators_button" = "Delete Authenticators"; @@ -23,6 +24,11 @@ "authenticator_selection_authenticator_is_not_enrolled" = "Before using the authenticator, enroll it in the phone System Settings"; "authenticator_selection_authenticator_is_not_policy_compliant" = "This authentication method cannot be used with your application"; +// Credential screen +"credential_done_button" = "Done"; +"credential_confirm_button" = "Confirm"; +"credential_cancel_button" = "Cancel"; + // Pin screen "pin_enrollment_title" = "Create PIN"; "pin_enrollment_description" = "Please define a six digit PIN."; @@ -32,9 +38,6 @@ "pin_change_description" = "Please define a six digit PIN."; "pin_old_pin_placeholder" = "Enter old PIN"; "pin_pin_placeholder" = "Enter PIN"; -"pin_done_button" = "Done"; -"pin_confirm_button" = "Confirm"; -"pin_cancel_button" = "Cancel"; "pin_missing_old_pin" = "Missing old PIN."; "pin_missing_pin" = "Missing PIN."; "pin_protection_status_locked_out" = "Your PIN is blocked."; @@ -43,6 +46,23 @@ "pin_protection_status_retries_with_cool_down" = "You have %@ tries left.\nPlease retry in %@ seconds."; "pin_protection_status_retries_without_cool_down" = "You have %@ tries left."; +// Password screen +"password_enrollment_title" = "Create Password"; +"password_enrollment_description" = "Please define a Password."; +"password_verify_title" = "Verify Password"; +"password_verify_description" = "Please enter your Password to complete the process."; +"password_change_title" = "Change Password"; +"password_change_description" = "Please define a Password."; +"password_old_password_placeholder" = "Enter old Password"; +"password_password_placeholder" = "Enter Password"; +"password_missing_old_password" = "Missing old Password."; +"password_missing_password" = "Missing Password."; +"password_protection_status_locked_out" = "Your Password is blocked."; +"password_protection_status_last_retry_with_cool_down" = "You have %@ try left.\nPlease retry in %@ seconds.\nAfter that your Password will be blocked."; +"password_protection_status_last_retry_without_cool_down" = "You have %@ try left.\nAfter that your Password will be blocked."; +"password_protection_status_retries_with_cool_down" = "You have %@ tries left.\nPlease retry in %@ seconds."; +"password_protection_status_retries_without_cool_down" = "You have %@ tries left."; + // Transaction Confirmation screen "transaction_confirmation_title" = "Transaction Confirmation"; "transaction_confirmation_confirm_button" = "Confirm"; @@ -91,6 +111,7 @@ "operation_authentication_title" = "Authentication"; "operation_deregistration_title" = "Deregistration"; "operation_pin_change_title" = "PIN change"; +"operation_password_change_title" = "Password change"; "operation_device_information_change_title" = "Device information change"; "operation_local_data_title" = "Local data operation"; @@ -103,6 +124,7 @@ "error_accounts_not_found_message" = "There are no registered accounts."; "error_authenticator_not_found_message" = "Authenticator not found."; "error_pin_authenticator_not_found_message" = "Pin authenticator not found."; +"error_password_authenticator_not_found_message" = "Password authenticator not found."; "error_device_information_not_found_message" = "No device information was found for the operation."; "error_auth_cloud_api_registration_missing_data_message" = "Either the response or the appLinkUri is required."; "error_auth_cloud_api_registration_wrong_data_message" = "You cannot provide both the response and the appLinkUri."; @@ -118,3 +140,4 @@ "authenticator_face_recognition_title" = "Face ID"; "authenticator_fingerprint_title" = "Touch ID"; "authenticator_device_passcode_title" = "Device Passcode"; +"authenticator_device_password_title" = "Password"; diff --git a/NevisExampleApp/Screens/Auth Cloud Api Registration/AuthCloudApiRegistrationPresenter.swift b/NevisExampleApp/Screens/Auth Cloud Api Registration/AuthCloudApiRegistrationPresenter.swift index 8390342..a6916b0 100644 --- a/NevisExampleApp/Screens/Auth Cloud Api Registration/AuthCloudApiRegistrationPresenter.swift +++ b/NevisExampleApp/Screens/Auth Cloud Api Registration/AuthCloudApiRegistrationPresenter.swift @@ -23,6 +23,9 @@ final class AuthCloudApiRegistrationPresenter { /// The PIN enroller. private let pinEnroller: PinEnroller + /// The Password enroller. + private let passwordEnroller: PasswordEnroller + /// The biometric user verifier. private let biometricUserVerifier: BiometricUserVerifier @@ -46,6 +49,7 @@ final class AuthCloudApiRegistrationPresenter { /// - clientProvider: The client provider. /// - authenticatorSelector: The authenticator selector used during registration. /// - pinEnroller: The PIN enroller. + /// - passwordEnroller: The Password enroller. /// - biometricUserVerifier: The biometric user verifier. /// - devicePasscodeUserVerifier: The device passcode user verifier. /// - appCoordinator: The application coordinator. @@ -54,6 +58,7 @@ final class AuthCloudApiRegistrationPresenter { init(clientProvider: ClientProvider, authenticatorSelector: AuthenticatorSelector, pinEnroller: PinEnroller, + passwordEnroller: PasswordEnroller, biometricUserVerifier: BiometricUserVerifier, devicePasscodeUserVerifier: DevicePasscodeUserVerifier, appCoordinator: AppCoordinator, @@ -62,6 +67,7 @@ final class AuthCloudApiRegistrationPresenter { self.clientProvider = clientProvider self.authenticatorSelector = authenticatorSelector self.pinEnroller = pinEnroller + self.passwordEnroller = passwordEnroller self.biometricUserVerifier = biometricUserVerifier self.devicePasscodeUserVerifier = devicePasscodeUserVerifier self.appCoordinator = appCoordinator @@ -92,6 +98,7 @@ extension AuthCloudApiRegistrationPresenter { .deviceInformation(deviceInformation) .authenticatorSelector(authenticatorSelector) .pinEnroller(pinEnroller) + .passwordEnroller(passwordEnroller) .biometricUserVerifier(biometricUserVerifier) .devicePasscodeUserVerifier(devicePasscodeUserVerifier) .onSuccess { diff --git a/NevisExampleApp/Screens/Credential/CredentialPresenter.swift b/NevisExampleApp/Screens/Credential/CredentialPresenter.swift new file mode 100644 index 0000000..759e6ee --- /dev/null +++ b/NevisExampleApp/Screens/Credential/CredentialPresenter.swift @@ -0,0 +1,440 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2022. Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +// MARK: - Navigation + +/// Navigation parameter of the Credential view. +protocol CredentialParameter: NavigationParameterizable {} + +/// Navigation parameter of the Credential view in case of PIN authenticator. +enum PinParameter: CredentialParameter { + /// Represents PIN enrollment. + /// + /// - Parameters: + /// - lastRecoverableError: The object that informs that an error occurred during PIN enrollment. + /// - handler: The PIN enrollment handler. + case enrollment(lastRecoverableError: PinEnrollmentError?, + handler: PinEnrollmentHandler) + + /// Represents PIN verification. + /// + /// - Parameters: + /// - protectionStatus: The object describing the PIN authenticator protection status. + /// - lastRecoverableError: The object that informs that an error occurred during PIN verification. + /// - handler: The PIN verification handler. + case verification(protectionStatus: PinAuthenticatorProtectionStatus, + lastRecoverableError: PinUserVerificationError?, + handler: PinUserVerificationHandler) + + /// Represents PIN change. + /// + /// - Parameters: + /// - protectionStatus: The object describing the PIN authenticator protection status. + /// - lastRecoverableError: The object that informs that an error occurred during PIN change. + /// - handler: The PIN change handler. + case credentialChange(protectionStatus: PinAuthenticatorProtectionStatus, + lastRecoverableError: PinChangeRecoverableError?, + handler: PinChangeHandler) +} + +/// Navigation parameter of the Credential view in case of Password authenticator. +enum PasswordParameter: CredentialParameter { + /// Represents Password enrollment. + /// + /// - Parameters: + /// - lastRecoverableError: The object that informs that an error occurred during Password enrollment. + /// - handler: The Password enrollment handler. + case enrollment(lastRecoverableError: PasswordEnrollmentError?, + handler: PasswordEnrollmentHandler) + + /// Represents Password verification. + /// + /// - Parameters: + /// - protectionStatus: The object describing the Password authenticator protection status. + /// - lastRecoverableError: The object that informs that an error occurred during Password verification. + /// - handler: The Password verification handler. + case verification(protectionStatus: PasswordAuthenticatorProtectionStatus, + lastRecoverableError: PasswordUserVerificationError?, + handler: PasswordUserVerificationHandler) + + /// Represents Password change. + /// + /// - Parameters: + /// - protectionStatus: The object describing the Password authenticator protection status. + /// - lastRecoverableError: The object that informs that an error occurred during Password change. + /// - handler: The Password change handler. + case credentialChange(protectionStatus: PasswordAuthenticatorProtectionStatus, + lastRecoverableError: PasswordChangeRecoverableError?, + handler: PasswordChangeHandler) +} + +// MARK: - Presenter + +/// Presenter of Credential view. +final class CredentialPresenter { + + /// Available credential operations. + enum CredentialOperation { + /// Enrollment operation. + case enrollment + /// Change operation. + case credentialChange + /// Verification operation. + case verification + } + + // MARK: - Properties + + /// The view of the presenter. + weak var view: CredentialView? + + /// The logger. + private let logger: SDKLogger + + /// The current credential type. + private var credentialType: AuthenticatorAaid = .Pin + + /// The current operation. + private var operation: CredentialOperation = .enrollment + + /// The cooldown timer. + private var coolDownTimer: InteractionCountDownTimer? + + // MARK: Pin + + /// The PIN authenticator protection status. + private var pinProtectionStatus: PinAuthenticatorProtectionStatus? + + /// Error that can occur during PIN enrollment. + private var pinEnrollmentError: PinEnrollmentError? + + /// Error that can occur during PIN verification. + private var pinVerificationError: PinUserVerificationError? + + /// Error that can occur during PIN change. + private var pinCredentialChangeError: PinChangeRecoverableError? + + /// The PIN enrollment handler. + private var pinEnrollmentHandler: PinEnrollmentHandler? + + /// The PIN verification handler. + private var pinVerificationHandler: PinUserVerificationHandler? + + /// The PIN change handler. + private var pinCredentialChangeHandler: PinChangeHandler? + + // MARK: Password + + /// The Password authenticator protection status. + private var passwordProtectionStatus: PasswordAuthenticatorProtectionStatus? + + /// Error that can occur during Password enrollment. + private var passwordEnrollmentError: PasswordEnrollmentError? + + /// Error that can occur during Password verification. + private var passwordVerificationError: PasswordUserVerificationError? + + /// Error that can occur during Password change. + private var passwordCredentialChangeError: PasswordChangeRecoverableError? + + /// The Password enrollment handler. + private var passwordEnrollmentHandler: PasswordEnrollmentHandler? + + /// The Password verification handler. + private var passwordVerificationHandler: PasswordUserVerificationHandler? + + /// The Password change handler. + private var passwordCredentialChangeHandler: PasswordChangeHandler? + + // MARK: - Initialization + + /// Creates a new instance. + /// + /// - Parameters: + /// - logger: The logger. + /// - parameter: The navigation parameter. + init(logger: SDKLogger, + parameter: NavigationParameterizable? = nil) { + self.logger = logger + setParameter(parameter as? CredentialParameter) + } + + /// :nodoc: + deinit { + os_log("CredentialPresenter", log: OSLog.deinit, type: .debug) + + // If it is not nil at this moment, it means that a concurrent operation is about to be started. + pinEnrollmentHandler?.cancel() + pinVerificationHandler?.cancel() + pinCredentialChangeHandler?.cancel() + + passwordEnrollmentHandler?.cancel() + passwordVerificationHandler?.cancel() + passwordCredentialChangeHandler?.cancel() + } +} + +// MARK: - Public Interface + +extension CredentialPresenter { + /// Returns the actual screen title based on the operation and credential type. + /// + /// - Returns: The actual screen title based on the operation and credential type. + func getTitle() -> String { + switch operation { + case .enrollment where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Enrollment.title + case .verification where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Verify.title + case .credentialChange where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Change.title + case .enrollment where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Enrollment.title + case .verification where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Verify.title + case .credentialChange where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Change.title + default: + String() + } + } + + /// Returns the actual screen description based on the operation and credential type. + /// + /// - Returns: The actual screen description based on the operation and credential type. + func getDescription() -> String { + switch operation { + case .enrollment where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Enrollment.description + case .verification where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Verify.description + case .credentialChange where credentialType == AuthenticatorAaid.Pin: + L10n.Credential.Pin.Change.description + case .enrollment where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Enrollment.description + case .verification where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Verify.description + case .credentialChange where credentialType == AuthenticatorAaid.Password: + L10n.Credential.Password.Change.description + default: + String() + } + } + + /// Returns the actual operation. + /// + /// - Returns: The actual operation. + func getOperation() -> CredentialOperation { + operation + } + + /// Returns the actual credential type. + /// + /// - Returns: The actual credential type. + func getCredentialType() -> AuthenticatorAaid { + credentialType + } + + /// Returns the actual last recoverable error based on the operation and credential type. + /// + /// - Returns: The actual last recoverable error based on the operation and credential type. + func getLastRecoverableError() -> String { + switch operation { + case .enrollment: + pinEnrollmentError?.localizedDescription ?? passwordEnrollmentError?.localizedDescription ?? String() + case .verification: + pinVerificationError?.localizedDescription ?? passwordVerificationError?.localizedDescription ?? String() + case .credentialChange: + pinCredentialChangeError?.localizedDescription ?? passwordCredentialChangeError?.localizedDescription ?? String() + } + } + + /// Handles the authenticator protection status. + /// + /// - Returns: The actual protection information based on the protection status and the credential type. + func getProtectionInfo() -> CredentialProtectionInformation { + if case .Pin = credentialType { + switch pinProtectionStatus { + case .Unlocked, .none: + logger.log("PIN authenticator is unlocked.") + return .init() + case let .LastAttemptFailed(remainingTries, coolDown): + logger.log("Last attempt failed using the PIN authenticator.") + logger.log("Remaining tries: \(remainingTries), cool down period: \(coolDown).") + if coolDown > 0 { + startCoolDownTimer(with: coolDown, remainingTries: remainingTries) + } + + return .init(message: pinProtectionStatus?.localizedDescription ?? String(), + isInCoolDown: coolDown > 0) + case .LockedOut: + logger.log("PIN authenticator is locked.") + return .init(message: pinProtectionStatus?.localizedDescription ?? String()) + case .some: + logger.log("Unknown PIN authenticator protection status.") + return .init() + } + } + else if case .Password = credentialType { + switch passwordProtectionStatus { + case .Unlocked, .none: + logger.log("Password authenticator is unlocked.") + return .init() + case let .LastAttemptFailed(remainingTries, coolDown): + logger.log("Last attempt failed using the Password authenticator.") + logger.log("Remaining tries: \(remainingTries), cool down period: \(coolDown).") + if coolDown > 0 { + startCoolDownTimer(with: coolDown, remainingTries: remainingTries) + } + + return .init(message: passwordProtectionStatus?.localizedDescription ?? String(), + isInCoolDown: coolDown > 0) + case .LockedOut: + logger.log("Password authenticator is locked.") + return .init(message: passwordProtectionStatus?.localizedDescription ?? String()) + case .some: + logger.log("Unknown Password authenticator protection status.") + return .init() + } + } + else { + return .init() + } + } + + /// Confirms the given credentials. + /// + /// - Parameters: + /// - oldCredential: The old credential. + /// - credential: The credential. + func confirm(oldCredential: String, credential: String) { + logger.log("Confirming entered credentials.") + view?.disableInteraction() + switch operation { + case .enrollment: + pinEnrollmentHandler?.pin(credential) + pinEnrollmentHandler = nil + passwordEnrollmentHandler?.password(credential) + passwordEnrollmentHandler = nil + case .verification: + pinVerificationHandler?.verify(credential) + pinVerificationHandler = nil + passwordVerificationHandler?.verify(credential) + passwordVerificationHandler = nil + case .credentialChange: + pinCredentialChangeHandler?.pins(oldCredential, credential) + pinCredentialChangeHandler = nil + passwordCredentialChangeHandler?.passwords(oldCredential, credential) + passwordCredentialChangeHandler = nil + } + } + + /// Cancels the actual operation. + func cancel() { + switch operation { + case .enrollment: + logger.log("Cancelling credential enrollment.") + pinEnrollmentHandler?.cancel() + pinEnrollmentHandler = nil + passwordEnrollmentHandler?.cancel() + passwordEnrollmentHandler = nil + case .verification: + logger.log("Cancelling credential verification.") + pinVerificationHandler?.cancel() + pinVerificationHandler = nil + passwordVerificationHandler?.cancel() + passwordVerificationHandler = nil + case .credentialChange: + logger.log("Cancelling credential change.") + pinCredentialChangeHandler?.cancel() + pinCredentialChangeHandler = nil + passwordCredentialChangeHandler?.cancel() + passwordCredentialChangeHandler = nil + } + } +} + +// MARK: - Actions + +private extension CredentialPresenter { + + /// Handles the recevied parameter. + /// + /// - Parameter paramter: The parameter to handle. + func setParameter(_ parameter: CredentialParameter?) { + guard let parameter else { + preconditionFailure("Parameter type mismatch!") + } + + if let parameter = parameter as? PinParameter { + credentialType = .Pin + switch parameter { + case let .enrollment(error, handler): + operation = .enrollment + pinEnrollmentError = error + pinEnrollmentHandler = handler + case let .verification(status, error, handler): + operation = .verification + pinProtectionStatus = status + pinVerificationError = error + pinVerificationHandler = handler + case let .credentialChange(status, error, handler): + operation = .credentialChange + pinProtectionStatus = status + pinCredentialChangeError = error + pinCredentialChangeHandler = handler + } + } + else if let parameter = parameter as? PasswordParameter { + credentialType = .Password + switch parameter { + case let .enrollment(error, handler): + operation = .enrollment + passwordEnrollmentError = error + passwordEnrollmentHandler = handler + case let .verification(status, error, handler): + operation = .verification + passwordProtectionStatus = status + passwordVerificationError = error + passwordVerificationHandler = handler + case let .credentialChange(status, error, handler): + operation = .credentialChange + passwordProtectionStatus = status + passwordCredentialChangeError = error + passwordCredentialChangeHandler = handler + } + } + else { + preconditionFailure("Parameter type mismatch!") + } + } + + /// Starts the cooldown timer. + /// + /// - Parameters: + /// - cooldown: The cooldown of the timer. + /// - remainingTries: The number of remaining tries. + func startCoolDownTimer(with coolDown: Int, remainingTries: Int) { + coolDownTimer = InteractionCountDownTimer(timerLifeTime: TimeInterval(coolDown)) { remainingCoolDown in + let localizedDescription = switch self.credentialType { + case .Pin: + PinAuthenticatorProtectionStatus.LastAttemptFailed(remainingTries: remainingTries, + coolDownTimeInSeconds: remainingCoolDown).localizedDescription + case .Password: + PasswordAuthenticatorProtectionStatus.LastAttemptFailed(remainingTries: remainingTries, + coolDownTimeInSeconds: remainingCoolDown).localizedDescription + default: + String() + } + self.view?.update(by: .init(message: localizedDescription, + isInCoolDown: remainingCoolDown > 0)) + } + + coolDownTimer?.start() + } +} diff --git a/NevisExampleApp/Screens/Pin/PinScreen.swift b/NevisExampleApp/Screens/Credential/CredentialScreen.swift similarity index 64% rename from NevisExampleApp/Screens/Pin/PinScreen.swift rename to NevisExampleApp/Screens/Credential/CredentialScreen.swift index 70d50f0..7d5324f 100644 --- a/NevisExampleApp/Screens/Pin/PinScreen.swift +++ b/NevisExampleApp/Screens/Credential/CredentialScreen.swift @@ -6,8 +6,8 @@ import UIKit -/// The Pin view. Used for Pin code creation verification and change. -class PinScreen: BaseScreen, Screen { +/// The Credential view. Used for PIN / Password creation verification and change. +class CredentialScreen: BaseScreen, Screen { // MARK: - UI @@ -17,11 +17,11 @@ class PinScreen: BaseScreen, Screen { /// The description label. private let descriptionLabel = NSLabel(style: .normal) - /// The text field for the old PIN. - private let oldPinField = NSTextField(placeholder: L10n.Pin.oldPinPlaceholder, returnKeyType: .next) + /// The text field for the old credential. + private let oldCredentialField = NSTextField(returnKeyType: .next) - /// The text field for the PIN. - private let pinField = NSTextField(placeholder: L10n.Pin.pinPlaceholder) + /// The text field for the credential. + private let credentialField = NSTextField() /// The error label. private let errorLabel = NSLabel(style: .error) @@ -30,10 +30,10 @@ class PinScreen: BaseScreen, Screen { private let infoMessageLabel = NSLabel(style: .info) /// The confirm button. - private let confirmButton = OutlinedButton(title: L10n.Pin.confirm) + private let confirmButton = OutlinedButton(title: L10n.Credential.confirm) /// The cancel button. - private let cancelButton = OutlinedButton(title: L10n.Pin.cancel) + private let cancelButton = OutlinedButton(title: L10n.Credential.cancel) /// The toolbar for the keyboard with type *numberPad*. private let keyboardToolbar = UIToolbar() @@ -41,14 +41,14 @@ class PinScreen: BaseScreen, Screen { // MARK: - Properties /// The presenter. - var presenter: PinPresenter! + var presenter: CredentialPresenter! // MARK: - Initialization /// Creates a new instance. /// /// - Parameter presenter: The presenter. - init(presenter: PinPresenter) { + init(presenter: CredentialPresenter) { super.init() self.presenter = presenter self.presenter.view = self @@ -56,13 +56,13 @@ class PinScreen: BaseScreen, Screen { /// :nodoc: deinit { - os_log("PinScreen", log: OSLog.deinit, type: .debug) + os_log("CredentialScreen", log: OSLog.deinit, type: .debug) } } // MARK: - Lifecycle -extension PinScreen { +extension CredentialScreen { /// Override of the `viewDidLoad()` lifecycle method. Sets up the user interface. override func viewDidLoad() { @@ -88,7 +88,7 @@ extension PinScreen { // MARK: - Setups /// :nodoc: -private extension PinScreen { +private extension CredentialScreen { func setupUI() { setupTitleLabel() @@ -120,7 +120,7 @@ private extension PinScreen { let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let doneButton = UIBarButtonItem(title: L10n.Pin.done, + let doneButton = UIBarButtonItem(title: L10n.Credential.done, style: .done, target: self, action: #selector(done)) @@ -132,10 +132,11 @@ private extension PinScreen { } func setupOldPinField() { - oldPinField.do { + oldCredentialField.do { addItem($0, topSpacing: 16) $0.setHeight(with: 40) - $0.keyboardType = .numberPad + $0.placeholder = presenter.getCredentialType() == .Pin ? L10n.Credential.Pin.oldPinPlaceholder : L10n.Credential.Password.oldPasswordPlaceholder + $0.keyboardType = presenter.getCredentialType() == .Pin ? .numberPad : .default $0.isSecureTextEntry = true $0.inputAccessoryView = keyboardToolbar $0.superview?.isHidden = presenter.getOperation() != .credentialChange @@ -143,10 +144,11 @@ private extension PinScreen { } func setupPinField() { - pinField.do { + credentialField.do { addItem($0, topSpacing: 16) $0.setHeight(with: 40) - $0.keyboardType = .numberPad + $0.placeholder = presenter.getCredentialType() == .Pin ? L10n.Credential.Pin.pinPlaceholder : L10n.Credential.Password.passwordPlaceholder + $0.keyboardType = presenter.getCredentialType() == .Pin ? .numberPad : .default $0.isSecureTextEntry = true $0.inputAccessoryView = keyboardToolbar } @@ -181,21 +183,21 @@ private extension PinScreen { } func setupTextFields() { - if let superview = oldPinField.superview, !superview.isHidden { - oldPinField.becomeFirstResponder() + if let superview = oldCredentialField.superview, !superview.isHidden { + oldCredentialField.becomeFirstResponder() } else { - pinField.becomeFirstResponder() + credentialField.becomeFirstResponder() } } } -// MARK: - PinView +// MARK: - CredentialView /// :nodoc: -extension PinScreen: PinView { +extension CredentialScreen: CredentialView { - func update(by protectionInfo: PinProtectionInformation) { + func update(by protectionInfo: CredentialProtectionInformation) { infoMessageLabel.text = protectionInfo.message confirmButton.isEnabled = !protectionInfo.isInCoolDown cancelButton.isEnabled = !protectionInfo.isInCoolDown @@ -205,7 +207,7 @@ extension PinScreen: PinView { // MARK: - Actions /// :nodoc: -private extension PinScreen { +private extension CredentialScreen { @objc func done() { @@ -214,18 +216,18 @@ private extension PinScreen { @objc func confirm() { - if presenter.getOperation() == .credentialChange, oldPinField.text.isEmptyOrNil { - errorLabel.text = L10n.Pin.missingOldPin + if presenter.getOperation() == .credentialChange, oldCredentialField.text.isEmptyOrNil { + errorLabel.text = presenter.getCredentialType() == .Pin ? L10n.Credential.Pin.missingOldPin : L10n.Credential.Password.missingOldPassword return } - guard let pin = pinField.text, !pin.isEmpty else { - errorLabel.text = L10n.Pin.missingPin + guard let credential = credentialField.text, !credential.isEmpty else { + errorLabel.text = presenter.getCredentialType() == .Pin ? L10n.Credential.Pin.missingPin : L10n.Credential.Password.missingPassword return } errorLabel.text = nil - presenter.confirm(oldPin: oldPinField.text ?? String(), pin: pin) + presenter.confirm(oldCredential: oldCredentialField.text ?? String(), credential: credential) } @objc diff --git a/NevisExampleApp/Screens/Pin/PinView.swift b/NevisExampleApp/Screens/Credential/CredentialView.swift similarity index 63% rename from NevisExampleApp/Screens/Pin/PinView.swift rename to NevisExampleApp/Screens/Credential/CredentialView.swift index 03a8a18..c20475d 100644 --- a/NevisExampleApp/Screens/Pin/PinView.swift +++ b/NevisExampleApp/Screens/Credential/CredentialView.swift @@ -6,11 +6,11 @@ import Foundation -/// View protocol for the ``PinScreen``. -protocol PinView: BaseView { +/// View protocol for the ``CredentialScreen``. +protocol CredentialView: BaseView { /// Updates the view with the protection information. /// /// - Parameter protectionInfo: The protection information. - func update(by protectionInfo: PinProtectionInformation) + func update(by protectionInfo: CredentialProtectionInformation) } diff --git a/NevisExampleApp/Screens/Pin/Model/PinProtectionInformation.swift b/NevisExampleApp/Screens/Credential/Model/CredentialProtectionInformation.swift similarity index 68% rename from NevisExampleApp/Screens/Pin/Model/PinProtectionInformation.swift rename to NevisExampleApp/Screens/Credential/Model/CredentialProtectionInformation.swift index 22d24c6..877bfd8 100644 --- a/NevisExampleApp/Screens/Pin/Model/PinProtectionInformation.swift +++ b/NevisExampleApp/Screens/Credential/Model/CredentialProtectionInformation.swift @@ -6,12 +6,12 @@ import Foundation -/// Represents Pin protection related information that is used by the PinScreen view. -struct PinProtectionInformation { +/// Represents the protection related information of a credential that is used by the CredentialScreen view. +struct CredentialProtectionInformation { // MARK: - Properties - /// Pin protection related message. + /// Protection related message. let message: String /// Flag that tells whether the authenticator is in cooldown. @@ -22,7 +22,7 @@ struct PinProtectionInformation { /// Creates a new instance. /// /// - Parameters: - /// - message: Pin protection related message. Default value is an empty string. + /// - message: Protection related message. Default value is an empty string. /// - isInCoolDown: Flag that tells whether the authenticator is in cooldown. Default value is false. init(message: String = "", isInCoolDown: Bool = false) { self.message = message diff --git a/NevisExampleApp/Screens/Home/HomePresenter.swift b/NevisExampleApp/Screens/Home/HomePresenter.swift index e2e0054..723bbfd 100644 --- a/NevisExampleApp/Screens/Home/HomePresenter.swift +++ b/NevisExampleApp/Screens/Home/HomePresenter.swift @@ -23,6 +23,9 @@ final class HomePresenter { /// The PIN changer. private let pinChanger: PinChanger + /// The Password changer. + private let passwordChanger: PasswordChanger + /// The application coordinator. private let appCoordinator: AppCoordinator @@ -45,18 +48,21 @@ final class HomePresenter { /// - configurationLoader: The configuration loader. /// - clientProvider: The client provider. /// - pinChanger: The PIN changer. + /// - passwordChanger: The Password changer. /// - appCoordinator: The application coordinator. /// - errorHandlerChain: The error handler chain. /// - logger: The logger. init(configurationLoader: ConfigurationLoader, clientProvider: ClientProvider, pinChanger: PinChanger, + passwordChanger: PasswordChanger, appCoordinator: AppCoordinator, errorHandlerChain: ErrorHandlerChain, logger: SDKLogger) { self.configurationLoader = configurationLoader self.clientProvider = clientProvider self.pinChanger = pinChanger + self.passwordChanger = passwordChanger self.appCoordinator = appCoordinator self.errorHandlerChain = errorHandlerChain self.logger = logger @@ -161,31 +167,34 @@ extension HomePresenter { } } - /// Starts PIN changing. - func changePin() { + /// Starts Credential changing. + func changeCredential(_ authenticatorType: AuthenticatorAaid) { + let operation: Operation = authenticatorType == .Pin ? .pinChange : .passwordChange + let authenticatorNotFoundError: AppError = authenticatorType == .Pin ? .pinAuthenticatorNotFound : .passwordAuthenticatorNotFound + // find enrolled accounts guard let accounts = mobileAuthenticationClient?.localData.accounts, !accounts.isEmpty else { - let operationError = OperationError(operation: .pinChange, + let operationError = OperationError(operation: operation, underlyingError: AppError.accountsNotFound) return errorHandlerChain.handle(error: operationError) } - // find PIN authenticator + // find Credential authenticator guard let authenticators = mobileAuthenticationClient?.localData.authenticators else { - let operationError = OperationError(operation: .pinChange, - underlyingError: AppError.pinAuthenticatorNotFound) + let operationError = OperationError(operation: operation, + underlyingError: authenticatorNotFoundError) return errorHandlerChain.handle(error: operationError) } - guard let pinAuthenticator = authenticators.filter({ $0.aaid == AuthenticatorAaid.Pin.rawValue }).first else { - let operationError = OperationError(operation: .pinChange, - underlyingError: AppError.pinAuthenticatorNotFound) + guard let credentialAuthenticator = authenticators.filter({ $0.aaid == authenticatorType.rawValue }).first else { + let operationError = OperationError(operation: operation, + underlyingError: authenticatorNotFoundError) return errorHandlerChain.handle(error: operationError) } - guard let enrollment = pinAuthenticator.userEnrollment as? SdkUserEnrollment else { - let operationError = OperationError(operation: .pinChange, - underlyingError: AppError.pinAuthenticatorNotFound) + guard let enrollment = credentialAuthenticator.userEnrollment as? SdkUserEnrollment else { + let operationError = OperationError(operation: operation, + underlyingError: authenticatorNotFoundError) return errorHandlerChain.handle(error: operationError) } @@ -197,16 +206,19 @@ extension HomePresenter { switch eligibleAccounts.count { case 0: - let operationError = OperationError(operation: .pinChange, + let operationError = OperationError(operation: operation, underlyingError: AppError.accountsNotFound) return errorHandlerChain.handle(error: operationError) - case 1: + case 1 where authenticatorType == .Pin: // do PIN change automatically doPinChange(for: eligibleAccounts.first!.username) + case 1 where authenticatorType == .Password: + // do PIN change automatically + doPasswordChange(for: eligibleAccounts.first!.username) default: // in case of multiple eligible accounts we have to show the account selection screen let parameter: SelectAccountParameter = .select(accounts: eligibleAccounts, - operation: .pinChange, + operation: operation, handler: nil, message: nil) appCoordinator.navigateToAccountSelection(with: parameter) @@ -310,6 +322,26 @@ private extension HomePresenter { .execute() } + /// Starts the Password change operation. + /// + /// - Parameter username: The username of the account whose PIN must be changed. + func doPasswordChange(for username: String) { + view?.disableInteraction() + mobileAuthenticationClient?.operations.passwordChange + .username(username) + .passwordChanger(passwordChanger) + .onSuccess { + self.logger.log("Password change succeeded.", color: .green) + self.appCoordinator.navigateToResult(with: .success(operation: .passwordChange)) + } + .onError { + self.logger.log("Password change failed.", color: .red) + let operationError = OperationError(operation: .passwordChange, underlyingError: $0) + self.errorHandlerChain.handle(error: operationError) + } + .execute() + } + /// Deletes all local authenticators of all accounts. /// /// - Parameters: diff --git a/NevisExampleApp/Screens/Home/HomeScreen.swift b/NevisExampleApp/Screens/Home/HomeScreen.swift index 6df7882..bc377d4 100644 --- a/NevisExampleApp/Screens/Home/HomeScreen.swift +++ b/NevisExampleApp/Screens/Home/HomeScreen.swift @@ -29,6 +29,9 @@ final class HomeScreen: BaseScreen, Screen { /// The PIN Change button. private let pinChangeButton = OutlinedButton(title: L10n.Home.changePin) + /// The Password Change button. + private let passwordChangeButton = OutlinedButton(title: L10n.Home.changePassword) + /// The Change Device Information button. private let changeDeviceInformationButton = OutlinedButton(title: L10n.Home.changeDeviceInformation) @@ -103,6 +106,7 @@ private extension HomeScreen { setupAuthenticateButton() setupDeregisterButton() setupPinChangeButton() + setupPasswordChangeButton() setupChangeDeviceInformationButton() setupAuthCloudApiRegisterButton() setupDeleteAuthenticatorsButton() @@ -154,6 +158,14 @@ private extension HomeScreen { } } + func setupPasswordChangeButton() { + passwordChangeButton.do { + addItemToBottom($0, spacing: 16) + $0.setHeight(with: 40) + $0.addTarget(self, action: #selector(changePassword), for: .touchUpInside) + } + } + func setupChangeDeviceInformationButton() { changeDeviceInformationButton.do { addItemToBottom($0, spacing: 16) @@ -215,7 +227,12 @@ private extension HomeScreen { @objc func changePin() { - presenter.changePin() + presenter.changeCredential(.Pin) + } + + @objc + func changePassword() { + presenter.changeCredential(.Password) } @objc diff --git a/NevisExampleApp/Screens/Pin/PinPresenter.swift b/NevisExampleApp/Screens/Pin/PinPresenter.swift deleted file mode 100644 index f7969a5..0000000 --- a/NevisExampleApp/Screens/Pin/PinPresenter.swift +++ /dev/null @@ -1,275 +0,0 @@ -// -// Nevis Mobile Authentication SDK Example App -// -// Copyright © 2022. Nevis Security AG. All rights reserved. -// - -import NevisMobileAuthentication - -/// Navigation parameter of the Pin view. -enum PinParameter: NavigationParameterizable { - /// Represents Pin enrollment - /// . - /// - Parameters: - /// - lastRecoverableError: The object that informs that an error occurred during PIN enrollment. - /// - handler: The PIN enrollment handler. - case enrollment(lastRecoverableError: PinEnrollmentError?, - handler: PinEnrollmentHandler) - - /// Represents Pin verification. - /// - /// - Parameters: - /// - protectionStatus: The object describing the PIN authenticator protection status. - /// - lastRecoverableError: The object that informs that an error occurred during PIN verification. - /// - handler: The PIN verification handler. - case verification(protectionStatus: PinAuthenticatorProtectionStatus, - lastRecoverableError: PinUserVerificationError?, - handler: PinUserVerificationHandler) - - /// Represents Pin change. - /// - /// - Parameters: - /// - protectionStatus: The object describing the PIN authenticator protection status. - /// - lastRecoverableError: The object that informs that an error occurred during PIN change. - /// - handler: The PIN change handler. - case credentialChange(protectionStatus: PinAuthenticatorProtectionStatus, - lastRecoverableError: PinChangeRecoverableError?, - handler: PinChangeHandler) -} - -/// Presenter of Pin view. -final class PinPresenter { - - /// Available PIN operations. - enum PinOperation { - /// PIN enrollment operation. - case enrollment - /// PIN change operation. - case credentialChange - /// PIN verification operation. - case verification - } - - // MARK: - Properties - - /// The view of the presenter. - weak var view: PinView? - - /// The logger. - private let logger: SDKLogger - - /// The PIN authenticator protection status. - private var protectionStatus: PinAuthenticatorProtectionStatus? - - /// Error that can occur during PIN enrollment. - private var enrollmentError: PinEnrollmentError? - - /// Error that can occur during PIN verification. - private var verificationError: PinUserVerificationError? - - /// Error that can occur during PIN change. - private var credentialChangeError: PinChangeRecoverableError? - - /// The PIN enrollment handler. - private var enrollmentHandler: PinEnrollmentHandler? - - /// The PIN verification handler. - private var verificationHandler: PinUserVerificationHandler? - - /// The PIN change handler. - private var credentialChangeHandler: PinChangeHandler? - - /// The current PIN operation. - private var operation: PinOperation = .enrollment - - /// The cooldown timer. - private var coolDownTimer: InteractionCountDownTimer? - - // MARK: - Initialization - - /// Creates a new instance. - /// - /// - Parameters: - /// - logger: The logger. - /// - parameter: The navigation parameter. - init(logger: SDKLogger, - parameter: NavigationParameterizable? = nil) { - self.logger = logger - setParameter(parameter as? PinParameter) - } - - /// :nodoc: - deinit { - os_log("PinPresenter", log: OSLog.deinit, type: .debug) - // If it is not nil at this moment, it means that a concurrent operation will be started. - enrollmentHandler?.cancel() - verificationHandler?.cancel() - credentialChangeHandler?.cancel() - } -} - -// MARK: - Public Interface - -extension PinPresenter { - - /// Returns the actual screen title based on the operation. - /// - /// - Returns: The actual screen title based on the operation. - func getTitle() -> String { - switch operation { - case .enrollment: - L10n.Pin.Enrollment.title - case .verification: - L10n.Pin.Verify.title - case .credentialChange: - L10n.Pin.Change.title - } - } - - /// Returns the actual screen description based on the operation. - /// - /// - Returns: The actual screen description based on the operation. - func getDescription() -> String { - switch operation { - case .enrollment: - L10n.Pin.Enrollment.description - case .verification: - L10n.Pin.Verify.description - case .credentialChange: - L10n.Pin.Change.description - } - } - - /// Returns the actual operation. - /// - /// - Returns: The actual operation. - func getOperation() -> PinOperation { - operation - } - - /// Returns the actual last recoverable error based on the operation. - /// - /// - Returns: The actual last recoverable error based on the operation. - func getLastRecoverableError() -> String { - switch operation { - case .enrollment: - enrollmentError?.localizedDescription ?? String() - case .verification: - verificationError?.localizedDescription ?? String() - case .credentialChange: - credentialChangeError?.localizedDescription ?? String() - } - } - - /// Handles the PIN authenticator protection status. - /// - /// - Returns: The actual protection information based on the protection status. - func getProtectionInfo() -> PinProtectionInformation { - switch protectionStatus { - case .Unlocked, .none: - logger.log("PIN authenticator is unlocked.") - return .init() - case let .LastAttemptFailed(remainingTries, coolDown): - logger.log("Last attempt failed using the PIN authenticator.") - logger.log("Remaining tries: \(remainingTries), cool down period: \(coolDown).") - if coolDown > 0 { - startCoolDownTimer(with: coolDown, remainingTries: remainingTries) - } - - return .init(message: protectionStatus?.localizedDescription ?? String(), - isInCoolDown: coolDown > 0) - case .LockedOut: - logger.log("PIN authenticator is locked.") - return .init(message: protectionStatus?.localizedDescription ?? String()) - case .some: - logger.log("Unknown PIN authenticator protection status.") - return .init() - } - } - - /// Confirms the given credentials. - /// - /// - Parameters: - /// - oldPin: The old PIN. - /// - pin: The PIN. - func confirm(oldPin: String, pin: String) { - logger.log("Confirming entered credentials.") - view?.disableInteraction() - switch operation { - case .enrollment: - enrollmentHandler?.pin(pin) - enrollmentHandler = nil - case .verification: - verificationHandler?.verify(pin) - verificationHandler = nil - case .credentialChange: - credentialChangeHandler?.pins(oldPin, pin) - credentialChangeHandler = nil - } - } - - /// Cancels the actual operation. - func cancel() { - switch operation { - case .enrollment: - logger.log("Cancelling PIN enrollment.") - enrollmentHandler?.cancel() - enrollmentHandler = nil - case .verification: - logger.log("Cancelling PIN verification.") - verificationHandler?.cancel() - verificationHandler = nil - case .credentialChange: - logger.log("Cancelling PIN change.") - credentialChangeHandler?.cancel() - credentialChangeHandler = nil - } - } -} - -// MARK: - Actions - -private extension PinPresenter { - - /// Handles the recevied parameter. - /// - /// - Parameter paramter: The parameter to handle. - func setParameter(_ parameter: PinParameter?) { - guard let parameter else { - preconditionFailure("Parameter type mismatch!") - } - - switch parameter { - case let .enrollment(error, handler): - operation = .enrollment - enrollmentError = error - enrollmentHandler = handler - case let .verification(status, error, handler): - operation = .verification - protectionStatus = status - verificationError = error - verificationHandler = handler - case let .credentialChange(status, error, handler): - operation = .credentialChange - protectionStatus = status - credentialChangeError = error - credentialChangeHandler = handler - } - } - - /// Starts the cooldown timer. - /// - /// - Parameters: - /// - cooldown: The cooldown of the timer. - /// - remainingTries: The number of remaining tries. - func startCoolDownTimer(with coolDown: Int, remainingTries: Int) { - coolDownTimer = InteractionCountDownTimer(timerLifeTime: TimeInterval(coolDown)) { remainingCoolDown in - let status: PinAuthenticatorProtectionStatus = .LastAttemptFailed(remainingTries: remainingTries, - coolDownTimeInSeconds: remainingCoolDown) - self.view?.update(by: .init(message: status.localizedDescription, - isInCoolDown: remainingCoolDown > 0)) - } - - coolDownTimer?.start() - } -} diff --git a/NevisExampleApp/Screens/Select Account/SelectAccountPresenter.swift b/NevisExampleApp/Screens/Select Account/SelectAccountPresenter.swift index 3d9bdb4..853a74e 100644 --- a/NevisExampleApp/Screens/Select Account/SelectAccountPresenter.swift +++ b/NevisExampleApp/Screens/Select Account/SelectAccountPresenter.swift @@ -41,6 +41,12 @@ final class SelectAccountPresenter { /// The PIN user verifier. private let pinUserVerifier: PinUserVerifier + /// The Password changer. + private let passwordChanger: PasswordChanger + + /// The Password user verifier. + private let passwordUserVerifier: PasswordUserVerifier + /// The biometric user verifier. private let biometricUserVerifier: BiometricUserVerifier @@ -82,6 +88,8 @@ final class SelectAccountPresenter { /// - authenticatorSelector: The authenticator selector used during in-band authentication. /// - pinChanger: The PIN changer. /// - pinUserVerifier: The PIN user verifier. + /// - passwordChanger: The Password changer. + /// - passwordUserVerifier: The Password user verifier. /// - biometricUserVerifier: The biometric user verifier. /// - devicePasscodeUserVerifier: The device passcode user verifier. /// - appCoordinator: The application coordinator. @@ -92,6 +100,8 @@ final class SelectAccountPresenter { authenticatorSelector: AuthenticatorSelector, pinChanger: PinChanger, pinUserVerifier: PinUserVerifier, + passwordChanger: PasswordChanger, + passwordUserVerifier: PasswordUserVerifier, biometricUserVerifier: BiometricUserVerifier, devicePasscodeUserVerifier: DevicePasscodeUserVerifier, appCoordinator: AppCoordinator, @@ -102,6 +112,8 @@ final class SelectAccountPresenter { self.authenticatorSelector = authenticatorSelector self.pinChanger = pinChanger self.pinUserVerifier = pinUserVerifier + self.passwordChanger = passwordChanger + self.passwordUserVerifier = passwordUserVerifier self.biometricUserVerifier = biometricUserVerifier self.devicePasscodeUserVerifier = devicePasscodeUserVerifier self.appCoordinator = appCoordinator @@ -148,6 +160,8 @@ extension SelectAccountPresenter { deregister(using: account) case .pinChange: changePin(using: account) + case .passwordChange: + changePassword(using: account) default: handler?.username(account.username) handler = nil @@ -180,6 +194,7 @@ private extension SelectAccountPresenter { .username(account.username) .authenticatorSelector(authenticatorSelector) .pinUserVerifier(pinUserVerifier) + .passwordUserVerifier(passwordUserVerifier) .biometricUserVerifier(biometricUserVerifier) .devicePasscodeUserVerifier(devicePasscodeUserVerifier) .onSuccess { // (authorizationProvider: AuthorizationProvider) in @@ -199,6 +214,8 @@ private extension SelectAccountPresenter { case let .FidoError(_, _, sessionProvider), let .NetworkError(_, sessionProvider): self.printSessionInfo(sessionProvider) + case .NoDeviceLockError: + fallthrough case .Unknown: fallthrough @unknown default: @@ -295,6 +312,27 @@ private extension SelectAccountPresenter { .execute() } + /// Changes the Password of the selected account. + /// + /// - Parameter account: The seleced account. + func changePassword(using account: any Account) { + logger.log("Changing Password for account: \(account)") + view?.disableInteraction() + mobileAuthenticationClient?.operations.passwordChange + .username(account.username) + .passwordChanger(passwordChanger) + .onSuccess { + self.logger.log("Password change succeeded.", color: .green) + self.appCoordinator.navigateToResult(with: .success(operation: .passwordChange)) + } + .onError { + self.logger.log("Password change failed.", color: .red) + let operationError = OperationError(operation: .passwordChange, underlyingError: $0) + self.errorHandlerChain.handle(error: operationError) + } + .execute() + } + /// Handles the recevied parameter. /// /// - Parameter paramter: The parameter to handle. diff --git a/NevisExampleApp/Screens/Username Password Login/UsernamePasswordLoginPresenter.swift b/NevisExampleApp/Screens/Username Password Login/UsernamePasswordLoginPresenter.swift index ce12bdd..9ca4ab9 100644 --- a/NevisExampleApp/Screens/Username Password Login/UsernamePasswordLoginPresenter.swift +++ b/NevisExampleApp/Screens/Username Password Login/UsernamePasswordLoginPresenter.swift @@ -30,6 +30,9 @@ final class UsernamePasswordLoginPresenter { /// The PIN enroller. private let pinEnroller: PinEnroller + /// The PIN enroller. + private let passwordEnroller: PasswordEnroller + /// The biometric user verifier. private let biometricUserVerifier: BiometricUserVerifier @@ -55,6 +58,7 @@ final class UsernamePasswordLoginPresenter { /// - clientProvider: The client provider. /// - authenticatorSelector: The authenticator selector. /// - pinEnroller: The PIN enroller. + /// - passwordEnroller: The Password enroller. /// - biometricUserVerifier: The biometric user verifier. /// - devicePasscodeUserVerifier: The device passcode user verifier. /// - appCoordinator: The application coordinator. @@ -65,6 +69,7 @@ final class UsernamePasswordLoginPresenter { clientProvider: ClientProvider, authenticatorSelector: AuthenticatorSelector, pinEnroller: PinEnroller, + passwordEnroller: PasswordEnroller, biometricUserVerifier: BiometricUserVerifier, devicePasscodeUserVerifier: DevicePasscodeUserVerifier, appCoordinator: AppCoordinator, @@ -75,6 +80,7 @@ final class UsernamePasswordLoginPresenter { self.clientProvider = clientProvider self.authenticatorSelector = authenticatorSelector self.pinEnroller = pinEnroller + self.passwordEnroller = passwordEnroller self.biometricUserVerifier = biometricUserVerifier self.devicePasscodeUserVerifier = devicePasscodeUserVerifier self.appCoordinator = appCoordinator @@ -162,6 +168,7 @@ private extension UsernamePasswordLoginPresenter { .authorizationProvider(CookieAuthorizationProvider(cookies)) .authenticatorSelector(authenticatorSelector) .pinEnroller(pinEnroller) + .passwordEnroller(passwordEnroller) .biometricUserVerifier(biometricUserVerifier) .devicePasscodeUserVerifier(devicePasscodeUserVerifier) .onSuccess { diff --git a/NevisExampleApp/Utility/Extensions/Authenticator+Extension.swift b/NevisExampleApp/Utility/Extensions/Authenticator+Extension.swift index ce7df26..4c6862a 100644 --- a/NevisExampleApp/Utility/Extensions/Authenticator+Extension.swift +++ b/NevisExampleApp/Utility/Extensions/Authenticator+Extension.swift @@ -19,6 +19,8 @@ extension Authenticator { L10n.Authenticator.Fingerprint.title case AuthenticatorAaid.DevicePasscode.rawValue: L10n.Authenticator.DevicePasscode.title + case AuthenticatorAaid.Password.rawValue: + L10n.Authenticator.Password.title default: String() } diff --git a/NevisExampleApp/Utility/Extensions/AuthenticatorAaid+Extensions.swift b/NevisExampleApp/Utility/Extensions/AuthenticatorAaid+Extensions.swift new file mode 100644 index 0000000..061392c --- /dev/null +++ b/NevisExampleApp/Utility/Extensions/AuthenticatorAaid+Extensions.swift @@ -0,0 +1,29 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024. Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// Codable extension for AuthenticatorAaids. +extension AuthenticatorAaid: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let aaid = try container.decode(String.self) + guard let authenticatorAaid = AuthenticatorAaid(rawValue: aaid) else { + throw NSError(domain: "AuthenticatorAaid cannot be decoced", code: 1) + } + self = authenticatorAaid + } +} + +extension AuthenticatorAaid { + static func == (lhs: String, rhs: AuthenticatorAaid) -> Bool { + rhs.rawValue == lhs + } + + static func == (lhs: AuthenticatorAaid, rhs: String) -> Bool { + rhs == lhs.rawValue + } +} diff --git a/NevisExampleApp/Utility/Extensions/PasswordAuthenticatorProtectionStatus+Extension.swift b/NevisExampleApp/Utility/Extensions/PasswordAuthenticatorProtectionStatus+Extension.swift new file mode 100644 index 0000000..5c42ac8 --- /dev/null +++ b/NevisExampleApp/Utility/Extensions/PasswordAuthenticatorProtectionStatus+Extension.swift @@ -0,0 +1,37 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024 Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +extension PasswordAuthenticatorProtectionStatus { + /// Returns the localized description. + var localizedDescription: String { + switch self { + case .Unlocked: + return String() + case .LockedOut: + return L10n.Credential.Password.ProtectionStatus.lockedOut + case let .LastAttemptFailed(remainingTries, coolDown): + var localizedDescription = "" + switch (remainingTries, coolDown) { + case (1, 0): + localizedDescription = L10n.Credential.Password.ProtectionStatus.lastRetryWithoutCoolDown(remainingTries) + case (1, 1...): + localizedDescription = L10n.Credential.Password.ProtectionStatus.lastRetryWithCoolDown(remainingTries, + String(format: "%.0f", Double(coolDown))) + case (2..., 1...): + localizedDescription = L10n.Credential.Password.ProtectionStatus.retriesWithCoolDown(remainingTries, + String(format: "%.0f", Double(coolDown))) + default: + localizedDescription = L10n.Credential.Password.ProtectionStatus.retriesWithoutCoolDown(remainingTries) + } + + return localizedDescription + @unknown default: + return String() + } + } +} diff --git a/NevisExampleApp/Utility/Extensions/PinAuthenticatorProtectionStatus+Extension.swift b/NevisExampleApp/Utility/Extensions/PinAuthenticatorProtectionStatus+Extension.swift index 9647702..54b9a35 100644 --- a/NevisExampleApp/Utility/Extensions/PinAuthenticatorProtectionStatus+Extension.swift +++ b/NevisExampleApp/Utility/Extensions/PinAuthenticatorProtectionStatus+Extension.swift @@ -13,20 +13,20 @@ extension PinAuthenticatorProtectionStatus { case .Unlocked: return String() case .LockedOut: - return L10n.Pin.ProtectionStatus.lockedOut + return L10n.Credential.Pin.ProtectionStatus.lockedOut case let .LastAttemptFailed(remainingTries, coolDown): var localizedDescription = "" switch (remainingTries, coolDown) { case (1, 0): - localizedDescription = L10n.Pin.ProtectionStatus.lastRetryWithoutCoolDown(remainingTries) + localizedDescription = L10n.Credential.Pin.ProtectionStatus.lastRetryWithoutCoolDown(remainingTries) case (1, 1...): - localizedDescription = L10n.Pin.ProtectionStatus.lastRetryWithCoolDown(remainingTries, - String(format: "%.0f", Double(coolDown))) + localizedDescription = L10n.Credential.Pin.ProtectionStatus.lastRetryWithCoolDown(remainingTries, + String(format: "%.0f", Double(coolDown))) case (2..., 1...): - localizedDescription = L10n.Pin.ProtectionStatus.retriesWithCoolDown(remainingTries, - String(format: "%.0f", Double(coolDown))) + localizedDescription = L10n.Credential.Pin.ProtectionStatus.retriesWithCoolDown(remainingTries, + String(format: "%.0f", Double(coolDown))) default: - localizedDescription = L10n.Pin.ProtectionStatus.retriesWithoutCoolDown(remainingTries) + localizedDescription = L10n.Credential.Pin.ProtectionStatus.retriesWithoutCoolDown(remainingTries) } return localizedDescription diff --git a/NevisExampleApp/Utility/Localization/Strings.swift b/NevisExampleApp/Utility/Localization/Strings.swift index 02776e0..ebca8d6 100644 --- a/NevisExampleApp/Utility/Localization/Strings.swift +++ b/NevisExampleApp/Utility/Localization/Strings.swift @@ -37,6 +37,8 @@ enum L10n { static let deregister = L10n.tr("home_deregister_button") /// Change PIN button: "PIN Change" static let changePin = L10n.tr("home_pin_change_button") + /// Change Password button: "Password Change" + static let changePassword = L10n.tr("home_password_change_button") /// Change device information button: "Change Device Information" static let changeDeviceInformation = L10n.tr("home_change_device_information_button") /// Auth Cloud Api registration button: "Auth Cloud Api Registration" @@ -65,96 +67,186 @@ enum L10n { static let authenticatorNotPolicyCompliant = L10n.tr("authenticator_selection_authenticator_is_not_policy_compliant") } - /// Pin screen related localized strings. - enum Pin { - /// Pin enrollment related localized strings. - enum Enrollment { - /// Screen title: "Create PIN" - static let title = L10n.tr("pin_enrollment_title") - /// Screen description: "Please define a six digit PIN." - static let description = L10n.tr("pin_enrollment_description") - } + /// Credential screen related localized strings. + enum Credential { + /// PIN related localized strings. + enum Pin { + /// Pin enrollment related localized strings. + enum Enrollment { + /// Screen title: "Create PIN" + static let title = L10n.tr("pin_enrollment_title") + /// Screen description: "Please define a six digit PIN." + static let description = L10n.tr("pin_enrollment_description") + } - /// Pin verification related localized strings. - enum Verify { - /// Screen title: "Verify PIN" - static let title = L10n.tr("pin_verify_title") - /// Screen description: "Please enter your PIN to complete the process." - static let description = L10n.tr("pin_verify_description") - } + /// PIN verification related localized strings. + enum Verify { + /// Screen title: "Verify PIN" + static let title = L10n.tr("pin_verify_title") + /// Screen description: "Please enter your PIN to complete the process." + static let description = L10n.tr("pin_verify_description") + } - /// Pin change related localized strings. - enum Change { - /// Screen title: "Change PIN" - static let title = L10n.tr("pin_change_title") - /// Screen description: "Please define a six digit PIN." - static let description = L10n.tr("pin_change_description") - } + /// PIN change related localized strings. + enum Change { + /// Screen title: "Change PIN" + static let title = L10n.tr("pin_change_title") + /// Screen description: "Please define a six digit PIN." + static let description = L10n.tr("pin_change_description") + } - /// Pin protection status related localized strings. - enum ProtectionStatus { - /// Screen description: "Please define a six digit PIN." - static let lockedOut = L10n.tr("pin_protection_status_locked_out") + /// PIN protection status related localized strings. + enum ProtectionStatus { + /// Screen description: "Please define a six digit PIN." + static let lockedOut = L10n.tr("pin_protection_status_locked_out") + + /// PIN protection status message for last retry with cool down: "You have %@ try left.\nPlease retry in %@ seconds.\nAfter that your PIN will be blocked." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - parameter coolDown: The cool down time. + /// - returns: The localized string. + static func lastRetryWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { + L10n.tr("pin_protection_status_last_retry_with_cool_down", + "Localizable", + String(describing: remainingTries), + String(describing: coolDown)) + } + + /// PIN protection status message for last retry without cool down: "You have %@ try left.\nAfter that your PIN will be blocked." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - returns: The localized string. + static func lastRetryWithoutCoolDown(_ remainingTries: Any) -> String { + L10n.tr("pin_protection_status_last_retry_without_cool_down", + "Localizable", + String(describing: remainingTries)) + } + + /// PIN protection status message for remaining retries with cool down: "You have %@ tries left.\nPlease retry in %@ seconds." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - parameter coolDown: The cool down time. + /// - returns: The localized string. + static func retriesWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { + L10n.tr("pin_protection_status_retries_with_cool_down", + "Localizable", + String(describing: remainingTries), + String(describing: coolDown)) + } + + /// PIN protection status message for remaining retries without cool down: "You have %@ tries left." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - returns: The localized string. + static func retriesWithoutCoolDown(_ remainingTries: Any) -> String { + L10n.tr("pin_protection_status_retries_without_cool_down", + "Localizable", + String(describing: remainingTries)) + } + } - /// Pin protection status message for last retry with cool down: "You have %@ try left.\nPlease retry in %@ seconds.\nAfter that your PIN will be blocked." - /// - /// - parameter remainingTries: The number of remaining retries. - /// - parameter coolDown: The cool down time. - /// - returns: The localized string. - static func lastRetryWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { - L10n.tr("pin_protection_status_last_retry_with_cool_down", - "Localizable", - String(describing: remainingTries), - String(describing: coolDown)) + /// Pin field placeholder: "Enter PIN" + static let pinPlaceholder = L10n.tr("pin_pin_placeholder") + /// PIN confirm field placeholder: "Enter old PIN" + static let oldPinPlaceholder = L10n.tr("pin_old_pin_placeholder") + /// Missing old PIN error: "Missing old PIN." + static let missingOldPin = L10n.tr("pin_missing_old_pin") + /// Missing PIN error: "Missing PIN." + static let missingPin = L10n.tr("pin_missing_pin") + } + + /// Password related localized strings. + enum Password { + /// Password enrollment related localized strings. + enum Enrollment { + /// Screen title: "Create Password" + static let title = L10n.tr("password_enrollment_title") + /// Screen description: "Please define a Password." + static let description = L10n.tr("password_enrollment_description") } - /// Pin protection status message for last retry without cool down: "You have %@ try left.\nAfter that your PIN will be blocked." - /// - /// - parameter remainingTries: The number of remaining retries. - /// - returns: The localized string. - static func lastRetryWithoutCoolDown(_ remainingTries: Any) -> String { - L10n.tr("pin_protection_status_last_retry_without_cool_down", - "Localizable", - String(describing: remainingTries)) + /// Password verification related localized strings. + enum Verify { + /// Screen title: "Verify Password" + static let title = L10n.tr("password_verify_title") + /// Screen description: "Please enter your Password to complete the process." + static let description = L10n.tr("password_verify_description") } - /// Pin protection status message for remaining retries with cool down: "You have %@ tries left.\nPlease retry in %@ seconds." - /// - /// - parameter remainingTries: The number of remaining retries. - /// - parameter coolDown: The cool down time. - /// - returns: The localized string. - static func retriesWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { - L10n.tr("pin_protection_status_retries_with_cool_down", - "Localizable", - String(describing: remainingTries), - String(describing: coolDown)) + /// Password change related localized strings. + enum Change { + /// Screen title: "Change Password" + static let title = L10n.tr("password_change_title") + /// Screen description: "Please define a six digit Password." + static let description = L10n.tr("password_change_description") } - /// Pin protection status message for remaining retries without cool down: "You have %@ tries left." - /// - /// - parameter remainingTries: The number of remaining retries. - /// - returns: The localized string. - static func retriesWithoutCoolDown(_ remainingTries: Any) -> String { - L10n.tr("pin_protection_status_retries_without_cool_down", - "Localizable", - String(describing: remainingTries)) + /// Password protection status related localized strings. + enum ProtectionStatus { + /// Screen description: "Please define a six digit Password." + static let lockedOut = L10n.tr("password_protection_status_locked_out") + + /// Password protection status message for last retry with cool down: "You have %@ try left.\nPlease retry in %@ seconds.\nAfter that your Password will be blocked." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - parameter coolDown: The cool down time. + /// - returns: The localized string. + static func lastRetryWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { + L10n.tr("password_protection_status_last_retry_with_cool_down", + "Localizable", + String(describing: remainingTries), + String(describing: coolDown)) + } + + /// Password protection status message for last retry without cool down: "You have %@ try left.\nAfter that your Password will be blocked." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - returns: The localized string. + static func lastRetryWithoutCoolDown(_ remainingTries: Any) -> String { + L10n.tr("password_protection_status_last_retry_without_cool_down", + "Localizable", + String(describing: remainingTries)) + } + + /// Password protection status message for remaining retries with cool down: "You have %@ tries left.\nPlease retry in %@ seconds." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - parameter coolDown: The cool down time. + /// - returns: The localized string. + static func retriesWithCoolDown(_ remainingTries: Any, _ coolDown: Any) -> String { + L10n.tr("password_protection_status_retries_with_cool_down", + "Localizable", + String(describing: remainingTries), + String(describing: coolDown)) + } + + /// Password protection status message for remaining retries without cool down: "You have %@ tries left." + /// + /// - parameter remainingTries: The number of remaining retries. + /// - returns: The localized string. + static func retriesWithoutCoolDown(_ remainingTries: Any) -> String { + L10n.tr("password_protection_status_retries_without_cool_down", + "Localizable", + String(describing: remainingTries)) + } } + + /// Password field placeholder: "Enter Password" + static let passwordPlaceholder = L10n.tr("password_password_placeholder") + /// Password confirm field placeholder: "Enter old Password" + static let oldPasswordPlaceholder = L10n.tr("password_old_password_placeholder") + /// Missing old Password error: "Missing old Password." + static let missingOldPassword = L10n.tr("password_missing_old_password") + /// Missing Password error: "Missing Password." + static let missingPassword = L10n.tr("password_missing_password") } - /// Pin field placeholder: "Enter PIN" - static let pinPlaceholder = L10n.tr("pin_pin_placeholder") - /// Pin confirm field placeholder: "Enter old PIN" - static let oldPinPlaceholder = L10n.tr("pin_old_pin_placeholder") /// Done button: "Done" - static let done = L10n.tr("pin_done_button") + static let done = L10n.tr("credential_done_button") /// Confirm button: "Confirm" - static let confirm = L10n.tr("pin_confirm_button") + static let confirm = L10n.tr("credential_confirm_button") /// Cancel button: "Cancel" - static let cancel = L10n.tr("pin_cancel_button") - /// Missing old PIN error: "Missing old PIN." - static let missingOldPin = L10n.tr("pin_missing_old_pin") - /// Missing PIN error: "Missing PIN." - static let missingPin = L10n.tr("pin_missing_pin") + static let cancel = L10n.tr("credential_cancel_button") } /// Transaction Confirmation screen related localized strings. @@ -187,9 +279,9 @@ enum L10n { /// Missing name error: "Missing name." static let missingName = L10n.tr("change_device_information_missing_name") /// Confirm button: "Confirm" - static let confirm = L10n.tr("transaction_confirmation_confirm_button") + static let confirm = L10n.tr("change_device_information_confirm_button") /// Confirm button: "Cancel" - static let cancel = L10n.tr("transaction_confirmation_cancel_button") + static let cancel = L10n.tr("change_device_information_cancel_button") } /// Auth Cloud Api Registration screen related localized strings. @@ -298,11 +390,17 @@ enum L10n { } /// PIN change operation related localized strings. - enum Pinchange { + enum PinChange { /// Operation title: "PIN change" static let title = L10n.tr("operation_pin_change_title") } + /// Password change operation related localized strings. + enum PasswordChange { + /// Operation title: "Password change" + static let title = L10n.tr("operation_password_change_title") + } + /// Device information change operation related localized strings. enum DeviceInformationChange { /// Operation title: "Device information change" @@ -366,6 +464,12 @@ enum L10n { /// Title: "Device Passcode" static let title = L10n.tr("authenticator_device_passcode_title") } + + /// Password authenticator related localized strings. + enum Password { + /// Title: "Password" + static let title = L10n.tr("authenticator_device_password_title") + } } /// Error related localized strings. @@ -392,6 +496,8 @@ enum L10n { static let authenticatorNotFound = L10n.tr("error_authenticator_not_found_message") /// PIN authenticator not found error message: "Pin authenticator not found." static let pinAuthenticatorNotFound = L10n.tr("error_pin_authenticator_not_found_message") + /// Password authenticator not found error message: "Password authenticator not found." + static let passwordAuthenticatorNotFound = L10n.tr("error_password_authenticator_not_found_message") /// Device information not found error message: "No device information was found for the operation." static let deviceInformationNotFound = L10n.tr("error_device_information_not_found_message") /// Auth Cloud registration data not found error message: "Either the response or the appLinkUri is required." diff --git a/NevisExampleApp/Utility/Validation/AuthenticatorValidator.swift b/NevisExampleApp/Utility/Validation/AuthenticatorValidator.swift new file mode 100644 index 0000000..a59e2bc --- /dev/null +++ b/NevisExampleApp/Utility/Validation/AuthenticatorValidator.swift @@ -0,0 +1,72 @@ +// +// Nevis Mobile Authentication SDK Example App +// +// Copyright © 2024. Nevis Security AG. All rights reserved. +// + +import NevisMobileAuthentication + +/// Class for validating list of authenticators. +final class AuthenticatorValidator {} + +// MARK: - Actions + +extension AuthenticatorValidator { + /// Validates authenticators for registration. + /// + /// - Parameter context: The context holding the accounts to validate. + /// - Parameter whitelistedAuthenticators: The array holding the whitelisted authenticators. + /// - Returns: The result of the validation + func validateForRegistration(context: AuthenticatorSelectionContext, whitelistedAuthenticators: [AuthenticatorAaid]) -> ValidationResult<[any Authenticator]> { + let allowedAuthenticators = allowedAuthenticators(context: context, whitelistedAuthenticators: whitelistedAuthenticators).filter { authenticator in + // Do not display: + // - policy non-compliant authenticators (this includes already registered authenticators) + // - not hardware supported authenticators. + authenticator.isSupportedByHardware && context.isPolicyCompliant(authenticatorAaid: authenticator.aaid) + } + + if allowedAuthenticators.isEmpty { + return .failure(AppError.authenticatorNotFound) + } + + return .success(allowedAuthenticators) + } + + /// Validates authenticators for authentication. + /// + /// - Parameter context: The context holding the accounts to validate. + /// - Parameter whitelistedAuthenticators: The array holding the whitelisted authenticators. + /// - Returns: The result of the validation + func validateForAuthentication(context: AuthenticatorSelectionContext, whitelistedAuthenticators: [AuthenticatorAaid]) -> ValidationResult<[any Authenticator]> { + let allowedAuthenticators = allowedAuthenticators(context: context, whitelistedAuthenticators: whitelistedAuthenticators).filter { authenticator in + guard let registration = authenticator.registration else { return false } + + // Do not display: + // - policy non-registered authenticators, + // - not hardware supported authenticators. + return authenticator.isSupportedByHardware && registration.isRegistered(context.account.username) + } + + if allowedAuthenticators.isEmpty { + return .failure(AppError.authenticatorNotFound) + } + + return .success(allowedAuthenticators) + } +} + +// MARK: Filtering based on the authenticator whitelist + +private extension AuthenticatorValidator { + /// Filters out non-whitelisted authenticators. + /// + /// - Parameter context: The context holding the accounts to validate. + /// - Parameter whitelistedAuthenticators: The array holding the whitelisted authenticators. + /// - Returns: The list of allowed authenticators. + func allowedAuthenticators(context: AuthenticatorSelectionContext, whitelistedAuthenticators: [AuthenticatorAaid]) -> [any Authenticator] { + context.authenticators.filter { authenticator in + guard let authenticatorAaid = AuthenticatorAaid(rawValue: authenticator.aaid) else { return false } + return whitelistedAuthenticators.contains(authenticatorAaid) + } + } +}