@nevis-security/nevis-mobile-authentication-sdk-react
Version:
React Native plugin for Nevis Mobile Authentication SDK. Supports only mobile.
447 lines (410 loc) • 17.5 kB
text/typescript
/**
* Copyright © 2023-2024 Nevis Security AG. All rights reserved.
*/
import uuid from 'react-native-uuid';
import { HttpOperation, HttpOperationImpl } from './HttpOperation';
import type { PasswordEnroller } from './password/PasswordEnroller';
import type { PinEnroller } from './pin/PinEnroller';
import type { AuthenticatorSelector } from './selection/AuthenticatorSelector';
import type { BiometricUserVerifier } from './userverification/BiometricUserVerifier';
import type { DevicePasscodeUserVerifier } from './userverification/DevicePasscodeUserVerifier';
import type { FingerprintUserVerifier } from './userverification/FingerprintUserVerifier';
import { UserInteractionPlatformOperationImpl } from '../cache/operation/UserInteractionPlatformOperation';
import { PlatformOperationCache } from '../cache/PlatformOperationCache';
import type { AuthCloudApiError } from '../error/authCloudApi/AuthCloudApiError';
import { AuthCloudApiErrorConverter } from '../error/authCloudApi/AuthCloudApiErrorConverter';
import { NativeEventListener } from '../event/NativeEventListener';
import { DeviceInformation } from '../localData/DeviceInformation';
import NevisMobileAuthenticationSdkReact from '../MobileAuthenticationSdk';
import { AuthCloudApiRegistrationMessage } from '../model/messages/out/AuthCloudApiRegistrationMessage';
/**
* The object that can be used to trigger a registration operation from the response to the
* [Authentication Cloud API enroll](https://docs.nevis.net/apidoc/authcloud/#enroll-your-access-app)
* request.
*
* Usage example:
* ```ts
* class AuthenticatorSelectorImpl extends AuthenticatorSelector {
* async selectAuthenticator(
* context: AuthenticatorSelectionContext,
* handler: AuthenticatorSelectionHandler
* ): Promise<void> {
* await handler.aaid(aaid).catch(console.error);
* }
* }
*
* class BiometricUserVerifierImpl extends BiometricUserVerifier {
* async verifyBiometric(
* context: BiometricUserVerificationContext,
* handler: BiometricUserVerificationHandler
* ): Promise<void> {
* await handler
* .listenForOsCredentials(
* BiometricPromptOptions.create(
* 'Biometric authentication required',
* 'Cancel',
* 'Please identify yourself.'
* )
* )
* .catch(console.error);
* }
* }
*
* async register(
* client: MobileAuthenticationClient,
* enrollResponse: string,
* deviceInformation: DeviceInformation
* ): Promise<void> {
* await client.operations.authCloudApiRegistration
* .enrollResponse(enrollResponse)
* .deviceInformation(deviceInformation)
* .authenticatorSelector(new AuthenticatorSelectorImpl())
* .biometricUserVerifier(new BiometricUserVerifierImpl())
* .onSuccess(() => {
* // handle success
* })
* .onError((_error) => {
* // handle error
* })
* .execute();
* }
* ```
*
* The biometric, device passcode and fingerprint authenticators are enrolled at the OS level. That is why,
* if one of them must be registered, the user must authenticate through {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier}.
* In the case of the PIN and password, the credentials are enrolled during, so no authentication is needed.
*
* @see {@link Operations.authCloudApiRegistration}
*/
export abstract class AuthCloudApiRegistration extends HttpOperation<AuthCloudApiRegistration> {
/**
* Specifies the response to the [Authentication Cloud API enroll](https://docs.nevis.net/apidoc/authcloud/#enroll-your-access-app)
* request.
*
* **IMPORTANT** \
* You must provide either the whole response through this method, or the URL with the {@link appLinkUri}
* method. Only one of them can be provided.
*
* @param enrollResponse the enroll response in JSON format.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract enrollResponse(enrollResponse: string): AuthCloudApiRegistration;
/**
* Specifies the value of the `appLinkUri` attribute in the enroll response sent by the server.
*
* The URL has the following format `https://{instance}-app.mauth.nevis.cloud/open?dispatchTokenResponse=<dispatchTokenResponse>`.
*
* **IMPORTANT** \
* You must provide either the `appLinkUri` through this method, or the whole response with the
* {@link enrollResponse} method. Only one of them can be provided.
*
* @param appLinkUri the URL contained in the `appLinkUri` attribute value.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract appLinkUri(appLinkUri: string): AuthCloudApiRegistration;
/**
* Specifies the device information to be used.
*
* The {@link DeviceInformation} is required only if you require support for encrypted out-of-band
* payloads or push notifications. If a {@link DeviceInformation} was already provided in an
* existing registration, the provided value will be ignored.
*
* @param deviceInformation the device information.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract deviceInformation(deviceInformation: DeviceInformation): AuthCloudApiRegistration;
/**
* Specifies whether [Class 2 (formerly weak)](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
* biometric sensors are allowed if the biometric authenticator is selected.
*
* **IMPORTANT** \
* This method is Android specific and will be ignored on iOS platform.
*
* By default, the SDK will only allow to use Class 3 (formerly strong) sensors. Using Class 2
* sensors is less secure and discouraged. When a Class 2 sensor is used, the FIDO UAF keys are
* not protected by the operating system by requiring user authentication.
*
* If the SDK detects that only Class 3 (strong) biometric sensors are available in the mobile
* device, even if Class 2 sensors are allowed, the FIDO UAF credentials will be protected by
* the operating system by requiring user authentication.
*
* However, in some cases it may be acceptable for the sake of end-user convenience. Allowing
* Class 2 sensors will enable for instance the use of face recognition in some Samsung devices.
*
* @param allowClass2AndroidSensors specifies whether Class 2 biometric sensors are allowed if
* the biometric authenticator is selected.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract allowClass2AndroidSensors(
allowClass2AndroidSensors: boolean
): AuthCloudApiRegistration;
/**
* Specifies whether the OS device passcode can be used as fallback during biometric authentication.
*
* If not specified, the device passcode cannot be used as fallback.
*
* @param allowDevicePasscodeAsFallback indicates whether the device passcode can be used as fallback.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract allowDevicePasscodeAsFallback(
allowDevicePasscodeAsFallback: boolean
): AuthCloudApiRegistration;
/**
* Specifies whether the authenticator must be invalidated if the user adds new biometric
* credentials in the OS settings. If the authenticator has been invalidated, and you try to
* authenticate with it, an error with code {@link FidoErrorCodeType.KeyDisappearedPermanently}
* will be returned by the authentication operation.
*
* This setting only applies to biometric {@link Aaid.BIOMETRIC} and fingerprint {@link Aaid.FINGERPRINT}
* authenticators.
* By setting this parameter to `true`, you increase the security but there is a loss of
* convenience: adding a new OS biometric credential does not imply necessarily that there is a
* security risk, but if the end-user does it, a new registration will be required, because an
* invalidated authenticator cannot be recovered.
*
* If not specified, the authenticator will be invalidated when the user adds a new biometric
* credential in the OS settings.
*
* @param invalidateOnNewOsBiometrics indicates whether an addition of biometric credentials in
* the OS should invalidate this authenticator.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract invalidateOnNewOsBiometrics(
invalidateOnNewOsBiometrics: boolean
): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of the selection of the authenticator to be used.
*
* **IMPORTANT** \
* Providing the authenticator selector is required.
*
* @param authenticatorSelector the {@link AuthenticatorSelector}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract authenticatorSelector(
authenticatorSelector: AuthenticatorSelector
): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of enrolling the PIN of the authenticator.
* It must be provided only if a PIN authenticator must be registered.
*
* **IMPORTANT** \
* Providing at least one of the {@link PinEnroller}, {@link PasswordEnroller}, {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier} is required.
*
* @param pinEnroller the {@link PinEnroller}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract pinEnroller(pinEnroller: PinEnroller): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of enrolling the password of the authenticator.
* It must be provided only if a password authenticator must be registered.
*
* **IMPORTANT** \
* Providing at least one of the {@link PinEnroller}, {@link PasswordEnroller}, {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier} is required.
*
* @param passwordEnroller the {@link PasswordEnroller}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract passwordEnroller(passwordEnroller: PasswordEnroller): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of the biometric user verification.
* It must be provided only if a biometric authenticator must be registered.
*
* **IMPORTANT** \
* Providing at least one of the {@link PinEnroller}, {@link PasswordEnroller}, {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier} is required.
*
* @param biometricUserVerifier the {@link BiometricUserVerifier}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract biometricUserVerifier(
biometricUserVerifier: BiometricUserVerifier
): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of the device passcode user verification.
* It must be provided only if a device passcode authenticator must be registered.
*
* **IMPORTANT** \
* Providing at least one of the {@link PinEnroller}, {@link PasswordEnroller}, {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier} is required.
*
* @param devicePasscodeUserVerifier the {@link DevicePasscodeUserVerifier}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract devicePasscodeUserVerifier(
devicePasscodeUserVerifier: DevicePasscodeUserVerifier
): AuthCloudApiRegistration;
/**
* Specifies the object that will take care of the fingerprint user verification.
* It must be provided only if a fingerprint authenticator must be registered.
*
* **IMPORTANT** \
* Providing at least one of the {@link PinEnroller}, {@link PasswordEnroller}, {@link BiometricUserVerifier},
* {@link DevicePasscodeUserVerifier} or {@link FingerprintUserVerifier} is required.
*
* @param fingerprintUserVerifier the {@link FingerprintUserVerifier}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract fingerprintUserVerifier(
fingerprintUserVerifier: FingerprintUserVerifier
): AuthCloudApiRegistration;
/**
* Specifies the object that will be invoked if the registration completed successfully.
*
* **IMPORTANT** \
* Providing the {@link onSuccess} is required.
*
* @param onSuccess the callback which is invoked on successful registration.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract onSuccess(onSuccess: () => void): AuthCloudApiRegistration;
/**
* Specifies the object that will be invoked if the registration failed.
*
* **IMPORTANT** \
* Providing the {@link onError} is required.
*
* @param onError the callback which receives an {@link AuthCloudApiError}.
* @returns an {@link AuthCloudApiRegistration} object.
*/
abstract onError(onError: (error: AuthCloudApiError) => void): AuthCloudApiRegistration;
}
export class AuthCloudApiRegistrationImpl
extends HttpOperationImpl<AuthCloudApiRegistration>
implements AuthCloudApiRegistration
{
private _enrollResponse?: string;
private _appLinkUri?: string;
private _deviceInformation?: DeviceInformation;
private _allowClass2AndroidSensors?: boolean;
private _allowDevicePasscodeAsFallback?: boolean;
private _invalidateOnNewOsBiometrics?: boolean;
private _authenticatorSelector?: AuthenticatorSelector;
private _pinEnroller?: PinEnroller;
private _passwordEnroller?: PasswordEnroller;
private _biometricUserVerifier?: BiometricUserVerifier;
private _devicePasscodeUserVerifier?: DevicePasscodeUserVerifier;
private _fingerprintUserVerifier?: FingerprintUserVerifier;
private _onSuccess?: () => void;
private _onError?: (error: AuthCloudApiError) => void;
enrollResponse(enrollResponse: string): AuthCloudApiRegistration {
this._enrollResponse = enrollResponse;
return this;
}
appLinkUri(appLinkUri: string): AuthCloudApiRegistration {
this._appLinkUri = appLinkUri;
return this;
}
deviceInformation(deviceInformation: DeviceInformation): AuthCloudApiRegistration {
this._deviceInformation = deviceInformation;
return this;
}
allowClass2AndroidSensors(allowClass2AndroidSensors: boolean): AuthCloudApiRegistration {
this._allowClass2AndroidSensors = allowClass2AndroidSensors;
return this;
}
allowDevicePasscodeAsFallback(
allowDevicePasscodeAsFallback: boolean
): AuthCloudApiRegistration {
this._allowDevicePasscodeAsFallback = allowDevicePasscodeAsFallback;
return this;
}
invalidateOnNewOsBiometrics(invalidateOnNewOsBiometrics: boolean): AuthCloudApiRegistration {
this._invalidateOnNewOsBiometrics = invalidateOnNewOsBiometrics;
return this;
}
authenticatorSelector(authenticatorSelector: AuthenticatorSelector): AuthCloudApiRegistration {
this._authenticatorSelector = authenticatorSelector;
return this;
}
pinEnroller(pinEnroller: PinEnroller): AuthCloudApiRegistration {
this._pinEnroller = pinEnroller;
return this;
}
passwordEnroller(passwordEnroller: PasswordEnroller): AuthCloudApiRegistration {
this._passwordEnroller = passwordEnroller;
return this;
}
biometricUserVerifier(biometricUserVerifier: BiometricUserVerifier): AuthCloudApiRegistration {
this._biometricUserVerifier = biometricUserVerifier;
return this;
}
devicePasscodeUserVerifier(
devicePasscodeUserVerifier: DevicePasscodeUserVerifier
): AuthCloudApiRegistration {
this._devicePasscodeUserVerifier = devicePasscodeUserVerifier;
return this;
}
fingerprintUserVerifier(
fingerprintUserVerifier: FingerprintUserVerifier
): AuthCloudApiRegistration {
this._fingerprintUserVerifier = fingerprintUserVerifier;
return this;
}
onSuccess(onSuccess: () => void): AuthCloudApiRegistration {
this._onSuccess = onSuccess;
return this;
}
onError(onError: (error: AuthCloudApiError) => void): AuthCloudApiRegistration {
this._onError = onError;
return this;
}
async execute(): Promise<void> {
const operationId = uuid.v4() as string;
const operation = new UserInteractionPlatformOperationImpl(
operationId,
undefined,
this._authenticatorSelector,
this._pinEnroller,
this._passwordEnroller,
undefined,
undefined,
undefined,
undefined,
this._biometricUserVerifier,
this._devicePasscodeUserVerifier,
this._fingerprintUserVerifier
);
PlatformOperationCache.getInstance().put(operation);
NativeEventListener.getInstance().start(operationId);
const message = new AuthCloudApiRegistrationMessage(
operationId,
false,
this._authenticatorSelector !== undefined,
this._pinEnroller !== undefined,
this._passwordEnroller !== undefined,
false,
false,
this._biometricUserVerifier !== undefined,
this._devicePasscodeUserVerifier !== undefined,
this._fingerprintUserVerifier !== undefined,
this._onSuccess !== undefined,
this._onError !== undefined,
this.httpRequestHeaders,
this._enrollResponse,
this._appLinkUri,
this._deviceInformation,
this._pinEnroller?.pinPolicy,
this._allowClass2AndroidSensors,
this._allowDevicePasscodeAsFallback,
this._invalidateOnNewOsBiometrics
);
function finish() {
NativeEventListener.getInstance().stop(operationId);
PlatformOperationCache.getInstance().delete(operationId);
}
return NevisMobileAuthenticationSdkReact.authCloudApiRegister(message)
.then(() => {
finish();
this._onSuccess?.();
})
.catch((error: Error) => {
finish();
const authCloudApiError = new AuthCloudApiErrorConverter(error).convert();
this._onError?.(authCloudApiError);
});
}
}