Skip to content

Commit

Permalink
Merge pull request #20 from nevissecurity/feature/NEVISACCESSAPP-5644-…
Browse files Browse the repository at this point in the history
…biometric-authentication-confirmation

NEVISACCESSAPP-5644: Add Biometric authentication confirmation and cancellation
  • Loading branch information
tamas-toth authored Mar 25, 2024
2 parents 3072aab + 021afa5 commit ec44696
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 29 deletions.
24 changes: 24 additions & 0 deletions NevisExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
38C1B385269DB56C00ED5782 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C1B384269DB56C00ED5782 /* UINavigationController+Extension.swift */; };
38C2D11D290BC20800E41764 /* ResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C2D11B290BC20800E41764 /* ResultViewModel.swift */; };
38C2D11E290BC20800E41764 /* ResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C2D11C290BC20800E41764 /* ResultScreen.swift */; };
38C6F3572BB17398002909BE /* ConfirmationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C6F3562BB17398002909BE /* ConfirmationViewModel.swift */; };
38C6F3592BB173A1002909BE /* ConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C6F3582BB173A1002909BE /* ConfirmationScreen.swift */; };
38C6F35B2BB17778002909BE /* VerifyBiometricResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C6F35A2BB17778002909BE /* VerifyBiometricResponse.swift */; };
38C6F35D2BB178F3002909BE /* VerifyDevicePasscodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C6F35C2BB178F3002909BE /* VerifyDevicePasscodeResponse.swift */; };
38CAEE9B2B021F9D00143059 /* DeleteAuthenticatorsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CAEE9A2B021F9D00143059 /* DeleteAuthenticatorsUseCase.swift */; };
38CAEE9D2B021FA600143059 /* DeleteAuthenticatorsUseCaseImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CAEE9C2B021FA600143059 /* DeleteAuthenticatorsUseCaseImpl.swift */; };
38D67EC926C69BCC0068334F /* VerifyPinResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D67EC826C69BCC0068334F /* VerifyPinResponse.swift */; };
Expand Down Expand Up @@ -271,6 +275,10 @@
38C1B384269DB56C00ED5782 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
38C2D11B290BC20800E41764 /* ResultViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultViewModel.swift; sourceTree = "<group>"; };
38C2D11C290BC20800E41764 /* ResultScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultScreen.swift; sourceTree = "<group>"; };
38C6F3562BB17398002909BE /* ConfirmationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationViewModel.swift; sourceTree = "<group>"; };
38C6F3582BB173A1002909BE /* ConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationScreen.swift; sourceTree = "<group>"; };
38C6F35A2BB17778002909BE /* VerifyBiometricResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyBiometricResponse.swift; sourceTree = "<group>"; };
38C6F35C2BB178F3002909BE /* VerifyDevicePasscodeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyDevicePasscodeResponse.swift; sourceTree = "<group>"; };
38CAEE9A2B021F9D00143059 /* DeleteAuthenticatorsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAuthenticatorsUseCase.swift; sourceTree = "<group>"; };
38CAEE9C2B021FA600143059 /* DeleteAuthenticatorsUseCaseImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAuthenticatorsUseCaseImpl.swift; sourceTree = "<group>"; };
38D67EC826C69BCC0068334F /* VerifyPinResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyPinResponse.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -379,6 +387,8 @@
381D598A26C5605F006B02DD /* OperationResponse.swift */,
3867A61E29013B23006EE5A6 /* SelectAccountResponse.swift */,
381D598226C56003006B02DD /* SelectAuthenticatorResponse.swift */,
38C6F35A2BB17778002909BE /* VerifyBiometricResponse.swift */,
38C6F35C2BB178F3002909BE /* VerifyDevicePasscodeResponse.swift */,
38D67EC826C69BCC0068334F /* VerifyPinResponse.swift */,
);
path = Model;
Expand Down Expand Up @@ -507,6 +517,7 @@
38B085A92907F5DD006295D8 /* Auth Cloud Api Registration */,
3852F3A72686246A0051FF73 /* Base */,
38ABBB502906AD1E006B19B9 /* Change Device Information */,
38C6F3552BB17387002909BE /* Confirmation */,
38FE58C22693442B0014DF93 /* Home */,
3852F3A4268624470051FF73 /* Launch */,
382D51AC26B194DC00B774CF /* Logging */,
Expand Down Expand Up @@ -855,6 +866,15 @@
path = Result;
sourceTree = "<group>";
};
38C6F3552BB17387002909BE /* Confirmation */ = {
isa = PBXGroup;
children = (
38C6F3562BB17398002909BE /* ConfirmationViewModel.swift */,
38C6F3582BB173A1002909BE /* ConfirmationScreen.swift */,
);
path = Confirmation;
sourceTree = "<group>";
};
38DABD6B29D58ADB008B8116 /* Username Password Login */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1067,6 +1087,7 @@
38ED024426E0B6BF00FAFC38 /* InteractionCountDownTimer.swift in Sources */,
38EFD3D326DCB9B30051A19E /* SharedSequenceConvertibleType+Extension.swift in Sources */,
38F0767E28FFD42900EFDAF8 /* OutOfBandOperationUseCaseImpl.swift in Sources */,
38C6F3572BB17398002909BE /* ConfirmationViewModel.swift in Sources */,
38B0A2BD269C477900FABCC1 /* ErrorHandlerChainImpl.swift in Sources */,
38EB17792902B49600E82236 /* BusinessError.swift in Sources */,
3875ADD1291BF6D100939D16 /* PinChangerImpl.swift in Sources */,
Expand Down Expand Up @@ -1125,12 +1146,14 @@
38B0A2C3269C483D00FABCC1 /* GeneralErrorHandler.swift in Sources */,
382D51AE26B194EB00B774CF /* LoggingScreen.swift in Sources */,
386DA8FE26CA9B9A00060ADD /* Operation.swift in Sources */,
38C6F3592BB173A1002909BE /* ConfirmationScreen.swift in Sources */,
38DF72802901657700429337 /* ResponseObserverImpl.swift in Sources */,
38C1B385269DB56C00ED5782 /* UINavigationController+Extension.swift in Sources */,
38EB176D29027E7600E82236 /* ResponseEmitterImpl.swift in Sources */,
3852F3A9268624720051FF73 /* BaseScreen.swift in Sources */,
38FD7ACF28FEEA6800F64099 /* DecodePayloadUseCase.swift in Sources */,
38BAF1AD26B291B000725619 /* SDKLoggerImpl.swift in Sources */,
38C6F35B2BB17778002909BE /* VerifyBiometricResponse.swift in Sources */,
38DF72792901456100429337 /* AccountCell.swift in Sources */,
38ABBB542906AD49006B19B9 /* ChangeDeviceInformationViewModel.swift in Sources */,
38FD7AB128FE954700F64099 /* InitClientUseCaseImpl.swift in Sources */,
Expand All @@ -1156,6 +1179,7 @@
38FD7AC128FEA30B00F64099 /* AppConfiguration.swift in Sources */,
386FC3342909262200CB30A7 /* LoginDataSourceImpl.swift in Sources */,
38FD7AB628FE981A00F64099 /* ClientProviderImpl.swift in Sources */,
38C6F35D2BB178F3002909BE /* VerifyDevicePasscodeResponse.swift in Sources */,
38EB1772290293B900E82236 /* TransactionConfirmationViewModel.swift in Sources */,
38B0A2BB269C474E00FABCC1 /* ErrorHandlerChain.swift in Sources */,
38BAF1AB26B291A700725619 /* SDKLogger.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ private extension AppAssembly {
argument: arg))
}.inObjectScope(.weak)

container.register(ConfirmationScreen.self) { (res: Resolver, arg: NavigationParameterizable) in
ConfirmationScreen(viewModel: res ~> (ConfirmationViewModel.self,
argument: arg))
}.inObjectScope(.weak)

container.register(ResultScreen.self) { (res: Resolver, arg: NavigationParameterizable) in
ResultScreen(viewModel: res ~> (ResultViewModel.self,
argument: arg))
Expand Down Expand Up @@ -146,6 +151,11 @@ private extension AppAssembly {
initializer: TransactionConfirmationViewModel.init)
.inObjectScope(.transient)

container.autoregister(ConfirmationViewModel.self,
argument: NavigationParameterizable.self,
initializer: ConfirmationViewModel.init)
.inObjectScope(.transient)

container.autoregister(ResultViewModel.self,
argument: NavigationParameterizable.self,
initializer: ResultViewModel.init)
Expand Down Expand Up @@ -191,7 +201,8 @@ private extension AppAssembly {
authenticatorSelector: authenticatorSelector,
pinEnroller: res~>,
biometricUserVerifier: res~>,
devicePasscodeUserVerifier: res~>)
devicePasscodeUserVerifier: res~>,
logger: res~>)
}

container.register(InBandAuthenticationUseCase.self) { res in
Expand Down
23 changes: 15 additions & 8 deletions NevisExampleApp/Domain/Interaction/BiometricUserVerifierImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,40 @@
import NevisMobileAuthentication

/// Default implementation of ``BiometricUserVerifier`` protocol.
///
/// With the help of the ``ResponseEmitter`` it will emit a ``VerifyBiometricResponse``.
class BiometricUserVerifierImpl {

// MARK: - Properties

/// The response emitter.
private let responseEmitter: ResponseEmitter

/// The logger.
private let logger: SDKLogger

// MARK: - Initialization

/// Creates a new instance.
///
/// - Parameter logger: The logger.
init(logger: SDKLogger) {
/// - Parameters:
/// - responseEmitter: The response emitter.
/// - logger: The logger.
init(responseEmitter: ResponseEmitter,
logger: SDKLogger) {
self.responseEmitter = responseEmitter
self.logger = logger
}
}

// MARK: - BiometricUserVerifier

extension BiometricUserVerifierImpl: BiometricUserVerifier {
func verifyBiometric(context _: BiometricUserVerificationContext, handler: BiometricUserVerificationHandler) {
func verifyBiometric(context: BiometricUserVerificationContext, handler: BiometricUserVerificationHandler) {
logger.log("Please start biometric user verification.")

// In case of face recognition authenticator (Face ID) you may show a confirmation screen
// because the OS provided popup doesn't give the possibility to cancel the process.
// In the example app automatic verification is selected.
logger.log("Performing automatic user verification.")
handler.verify()
let response = VerifyBiometricResponse(authenticator: context.authenticator.localizedTitle,
handler: handler)
responseEmitter.subject.onNext(response)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,40 @@
import NevisMobileAuthentication

/// Default implementation of ``DevicePasscodeUserVerifier`` protocol.
///
/// With the help of the ``ResponseEmitter`` it will emit a ``VerifyDevicePasscodeResponse``.
class DevicePasscodeUserVerifierImpl {

// MARK: - Properties

/// The response emitter.
private let responseEmitter: ResponseEmitter

/// The logger.
private let logger: SDKLogger

// MARK: - Initialization

/// Creates a new instance.
///
/// - Parameter logger: The logger.
init(logger: SDKLogger) {
/// - Parameters:
/// - responseEmitter: The response emitter.
/// - logger: The logger.
init(responseEmitter: ResponseEmitter,
logger: SDKLogger) {
self.responseEmitter = responseEmitter
self.logger = logger
}
}

// MARK: - DevicePasscodeUserVerifier

extension DevicePasscodeUserVerifierImpl: DevicePasscodeUserVerifier {
func verifyDevicePasscode(context _: DevicePasscodeUserVerificationContext, handler: DevicePasscodeUserVerificationHandler) {
func verifyDevicePasscode(context: DevicePasscodeUserVerificationContext, handler: DevicePasscodeUserVerificationHandler) {
logger.log("Please start device passcode user verification.")
logger.log("Performing automatic user verification.")
handler.verify()

let response = VerifyDevicePasscodeResponse(authenticator: context.authenticator.localizedTitle,
handler: handler)
responseEmitter.subject.onNext(response)
}
}
33 changes: 33 additions & 0 deletions NevisExampleApp/Domain/Model/VerifyBiometricResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Nevis Mobile Authentication SDK Example App
//
// Copyright © 2024 Nevis Security AG. All rights reserved.
//

import NevisMobileAuthentication

/// Response class indicating the SDK operation asks for user verification using a biometric authenticator.
final class VerifyBiometricResponse: OperationResponse {

// MARK: - Properties

/// The selected authenticator.
let authenticator: String

/// The object that is notified of the verification result.
let handler: BiometricUserVerificationHandler

// MARK: - Initialization

/// Creates a new instance.
///
/// - Parameters:
/// - authenticator: The selected authenticator.
/// - handler: The object that is notified of the verification result.
init(authenticator: String,
handler: BiometricUserVerificationHandler) {
self.authenticator = authenticator
self.handler = handler
super.init()
}
}
33 changes: 33 additions & 0 deletions NevisExampleApp/Domain/Model/VerifyDevicePasscodeResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Nevis Mobile Authentication SDK Example App
//
// Copyright © 2024 Nevis Security AG. All rights reserved.
//

import NevisMobileAuthentication

/// Response class indicating the SDK operation asks for user verification using the device passcode authenticator.
final class VerifyDevicePasscodeResponse: OperationResponse {

// MARK: - Properties

/// The selected authenticator.
let authenticator: String

/// The object that is notified of the verification result.
let handler: DevicePasscodeUserVerificationHandler

// MARK: - Initialization

/// Creates a new instance.
///
/// - Parameters:
/// - authenticator: The selected authenticator.
/// - handler: The object that is notified of the verification result.
init(authenticator: String,
handler: DevicePasscodeUserVerificationHandler) {
self.authenticator = authenticator
self.handler = handler
super.init()
}
}
15 changes: 13 additions & 2 deletions NevisExampleApp/Domain/Use Case/RegistrationUseCaseImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class RegistrationUseCaseImpl {
/// The device passcode user verifier.
private let devicePasscodeUserVerifier: DevicePasscodeUserVerifier

/// The logger.
private let logger: SDKLogger

// MARK: - Initialization

/// Creates a new instance.
Expand All @@ -41,18 +44,21 @@ class RegistrationUseCaseImpl {
/// - pinEnroller: The PIN enroller.
/// - biometricUserVerifier: The biometric user verifier.
/// - devicePasscodeUserVerifier: The device passcode user verifier.
/// - logger: The logger.
init(clientProvider: ClientProvider,
createDeviceInformationUseCase: CreateDeviceInformationUseCase,
authenticatorSelector: AuthenticatorSelector,
pinEnroller: PinEnroller,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier) {
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
logger: SDKLogger) {
self.clientProvider = clientProvider
self.createDeviceInformationUseCase = createDeviceInformationUseCase
self.authenticatorSelector = authenticatorSelector
self.pinEnroller = pinEnroller
self.biometricUserVerifier = biometricUserVerifier
self.devicePasscodeUserVerifier = devicePasscodeUserVerifier
self.logger = logger
}
}

Expand All @@ -77,10 +83,15 @@ extension RegistrationUseCaseImpl: RegistrationUseCase {
.biometricUserVerifier(biometricUserVerifier)
.devicePasscodeUserVerifier(devicePasscodeUserVerifier)
.onSuccess {
self.logger.log("In-Band registration succeeded.", color: .green)
observer.onNext(CompletedResponse(operation: .registration))
observer.onCompleted()
}
.onError(observer.onError)
.onError {
self.logger.log("In-Band registration failed.", color: .red)
observer.onError(OperationError(operation: .registration,
underlyingError: $0))
}

if let authorizationProvider {
operation?.authorizationProvider(authorizationProvider)
Expand Down
4 changes: 3 additions & 1 deletion NevisExampleApp/Domain/Validators/AccountValidatorImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ extension AccountValidatorImpl: AccountValidator {
supportedAuthenticators.forEach { authenticator in
authenticator.registration?.registeredAccounts.forEach { account in
if context.isPolicyCompliant(username: account.username, aaid: authenticator.aaid) {
accounts.append(account)
if !accounts.contains(where: { $0.username == account.username }) {
accounts.append(account)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ protocol AppCoordinator: Coordinator {
/// - Parameter parameter: The navigation parameter.
func navigateToTransactionConfirmation(with parameter: TransactionConfirmationParameter)

/// Navigates to the Confirmation screen.
///
/// - Parameter parameter: The navigation parameter.
func navigateToConfirmation(with parameter: ConfirmationParameter)

/// Navigates to the Result screen.
///
/// - Parameter parameter: The navigation parameter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ extension AppCoordinatorImpl: AppCoordinator {
rootNavigationController?.pushViewController(screen, animated: true)
}

func navigateToConfirmation(with parameter: ConfirmationParameter) {
guard let screen = DependencyProvider.shared.container.resolve(ConfirmationScreen.self,
argument: parameter as NavigationParameterizable) else {
return
}

logger.log("Navigating to Confirmation screen.", color: .purple)
rootNavigationController?.pushViewController(screen, animated: true)
}

func navigateToResult(with parameter: ResultParameter) {
guard let screen = DependencyProvider.shared.container.resolve(ResultScreen.self,
argument: parameter as NavigationParameterizable) else {
Expand Down
Loading

0 comments on commit ec44696

Please sign in to comment.