@nevis-security/nevis-mobile-authentication-sdk-react
Version:
React Native plugin for Nevis Mobile Authentication SDK. Supports only mobile.
355 lines (310 loc) • 11.6 kB
text/typescript
/**
* Copyright © 2023-2024 Nevis Security AG. All rights reserved.
*/
import {
type EmitterSubscription,
NativeEventEmitter,
NativeModules,
Platform,
} from 'react-native';
import { UserInteractionPlatformOperation } from '../cache/operation/UserInteractionPlatformOperation';
import { PlatformOperationCache } from '../cache/PlatformOperationCache';
import { AuthenticatorExtension } from '../extensions/AuthenticatorExtension';
import { OnValidCredentialsProvidedMessage } from '../model/messages/in/OnValidCredentialsProvidedMessage';
import { PasswordChangerMessage } from '../model/messages/in/PasswordChangerMessage';
import { PasswordEnrollerMessage } from '../model/messages/in/PasswordEnrollerMessage';
import { PasswordValidationMessage } from '../model/messages/in/PasswordValidationMessage';
import { PinChangerMessage } from '../model/messages/in/PinChangerMessage';
import { PinEnrollerMessage } from '../model/messages/in/PinEnrollerMessage';
import { PinValidationMessage } from '../model/messages/in/PinValidationMessage';
import { SelectAccountMessage } from '../model/messages/in/SelectAccountMessage';
import { SelectAuthenticatorMessage } from '../model/messages/in/SelectAuthenticatorMessage';
import { VerifyUserMessage } from '../model/messages/in/VerifyUserMessage';
enum EventType {
SelectAccount = 'selectAccount',
SelectAuthenticator = 'selectAuthenticator',
PinEnroll = 'pinEnroll',
PasswordEnroll = 'passwordEnroll',
PinChange = 'pinChange',
PasswordChange = 'passwordChange',
PinValidateForEnrollment = 'pinValidateForEnrollment',
PasswordValidateForEnrollment = 'passwordValidateForEnrollment',
PinValidateForPinChange = 'pinValidateForPinChange',
PasswordValidateForPasswordChange = 'passwordValidateForPasswordChange',
VerifyUser = 'verifyUser',
OnValidCredentialsProvided = 'onValidCredentialsProvided',
}
export class NativeEventListener {
private static instance: NativeEventListener;
private _ongoingOperations = new Set<string>();
private _eventEmitter: NativeEventEmitter;
private _selectAccount?: EmitterSubscription;
private _selectAuthenticator?: EmitterSubscription;
private _pinEnroll?: EmitterSubscription;
private _passwordEnroll?: EmitterSubscription;
private _pinChange?: EmitterSubscription;
private _passwordChange?: EmitterSubscription;
private _pinValidateForEnrollment?: EmitterSubscription;
private _passwordValidateForEnrollment?: EmitterSubscription;
private _pinValidateForPinChange?: EmitterSubscription;
private _passwordValidateForPasswordChange?: EmitterSubscription;
private _verifyUser?: EmitterSubscription;
private _onValidCredentialsProvided?: EmitterSubscription;
private constructor() {
this._eventEmitter = new NativeEventEmitter(NativeModules.RNEventEmitter);
}
static getInstance(): NativeEventListener {
if (!NativeEventListener.instance) {
NativeEventListener.instance = new NativeEventListener();
}
return NativeEventListener.instance;
}
start(operationId: string): void {
this._ongoingOperations.add(operationId);
this.startListeningForEvents();
}
stop(operationId: string): void {
this._ongoingOperations.delete(operationId);
if (this._ongoingOperations.size == 0) {
this.stopListeningForEvents();
}
}
private startListeningForEvents() {
this.listenToSelectAccount();
this.listenToSelectAuthenticator();
this.listenToPinEnroll();
this.listenToPasswordEnroll();
this.listenToPinChange();
this.listenToPasswordChange();
this.listenToPinValidateForEnrollment();
this.listenToPasswordValidateForEnrollment();
this.listenToPinValidateForPinChange();
this.listenToPasswordValidateForPasswordChange();
this.listenToVerifyUser();
this.listenToOnValidCredentialsProvided();
}
private stopListeningForEvents() {
this._selectAccount?.remove();
this._selectAccount = undefined;
this._selectAuthenticator?.remove();
this._selectAuthenticator = undefined;
this._pinEnroll?.remove();
this._pinEnroll = undefined;
this._passwordEnroll?.remove();
this._passwordEnroll = undefined;
this._pinChange?.remove();
this._pinChange = undefined;
this._passwordChange?.remove();
this._passwordChange = undefined;
this._pinValidateForEnrollment?.remove();
this._pinValidateForEnrollment = undefined;
this._passwordValidateForEnrollment?.remove();
this._passwordValidateForEnrollment = undefined;
this._pinValidateForPinChange?.remove();
this._pinValidateForPinChange = undefined;
this._passwordValidateForPasswordChange?.remove();
this._passwordValidateForPasswordChange = undefined;
this._verifyUser?.remove();
this._verifyUser = undefined;
this._onValidCredentialsProvided?.remove();
this._onValidCredentialsProvided = undefined;
}
private listenToSelectAccount(): void {
if (this._selectAccount) {
return; // Already listening
}
this._selectAccount = this._eventEmitter.addListener(EventType.SelectAccount, (data) => {
const message = SelectAccountMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during account selection.'
);
operation.selectAccount(message.context);
});
}
private listenToSelectAuthenticator(): void {
if (this._selectAuthenticator) {
return; // Already listening
}
this._selectAuthenticator = this._eventEmitter.addListener(
EventType.SelectAuthenticator,
(data) => {
const message = SelectAuthenticatorMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during authenticator selection.'
);
operation.selectAuthenticator(message.context);
}
);
}
private listenToPinEnroll(): void {
if (this._pinEnroll) {
return; // Already listening
}
this._pinEnroll = this._eventEmitter.addListener(EventType.PinEnroll, (data) => {
const message = PinEnrollerMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during PIN enrollment.'
);
operation.enrollPin(message.context);
});
}
private listenToPasswordEnroll(): void {
if (this._passwordEnroll) {
return; // Already listening
}
this._passwordEnroll = this._eventEmitter.addListener(EventType.PasswordEnroll, (data) => {
const message = PasswordEnrollerMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during password enrollment.'
);
operation.enrollPassword(message.context);
});
}
private listenToPinChange(): void {
if (this._pinChange) {
return; // Already listening
}
this._pinChange = this._eventEmitter.addListener(EventType.PinChange, (data) => {
const message = PinChangerMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during PIN change.'
);
operation.changePin(message.context);
});
}
private listenToPasswordChange(): void {
if (this._passwordChange) {
return; // Already listening
}
this._passwordChange = this._eventEmitter.addListener(EventType.PasswordChange, (data) => {
const message = PasswordChangerMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during password change.'
);
operation.changePassword(message.context);
});
}
private listenToPinValidateForEnrollment(): void {
if (this._pinValidateForEnrollment) {
return; // Already listening
}
this._pinValidateForEnrollment = this._eventEmitter.addListener(
EventType.PinValidateForEnrollment,
(data) => {
const message = PinValidationMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during PIN validation for enrollment.'
);
operation.validatePinForEnrollment(message.pin);
}
);
}
private listenToPasswordValidateForEnrollment(): void {
if (this._passwordValidateForEnrollment) {
return; // Already listening
}
this._passwordValidateForEnrollment = this._eventEmitter.addListener(
EventType.PasswordValidateForEnrollment,
(data) => {
const message = PasswordValidationMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during password validation for enrollment.'
);
operation.validatePasswordForEnrollment(message.password);
}
);
}
private listenToPinValidateForPinChange(): void {
if (this._pinValidateForPinChange) {
return; // Already listening
}
this._pinValidateForPinChange = this._eventEmitter.addListener(
EventType.PinValidateForPinChange,
(data) => {
const message = PinValidationMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during PIN validation for PIN change.'
);
operation.validatePinForPinChange(message.pin);
}
);
}
private listenToPasswordValidateForPasswordChange(): void {
if (this._passwordValidateForPasswordChange) {
return; // Already listening
}
this._passwordValidateForPasswordChange = this._eventEmitter.addListener(
EventType.PasswordValidateForPasswordChange,
(data) => {
const message = PasswordValidationMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during password validation for password change.'
);
operation.validatePasswordForPasswordChange(message.password);
}
);
}
private listenToVerifyUser(): void {
if (this._verifyUser) {
return; // Already listening
}
this._verifyUser = this._eventEmitter.addListener(EventType.VerifyUser, (data) => {
const message = VerifyUserMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during user verification.'
);
const handler = AuthenticatorExtension.handlerByAuthenticator(
message.context.authenticator.aaid,
message.operationId
);
operation.userVerificationHandler = handler;
PlatformOperationCache.getInstance().update(operation);
(async () => {
await operation.verifyUser(message.context, handler);
})();
});
}
private listenToOnValidCredentialsProvided(): void {
// listenToOnValidCredentialsProvided is only supported on an Android platform
if (Platform.OS !== 'android') {
return;
}
if (this._onValidCredentialsProvided) {
return; // Already listening
}
this._onValidCredentialsProvided = this._eventEmitter.addListener(
EventType.OnValidCredentialsProvided,
(data) => {
const message = OnValidCredentialsProvidedMessage.fromJson(data);
const operation = this.getUserInteractionPlatformOperation(
message.operationId,
'Operation not found during providing valid credentials.'
);
operation.userVerificationHandler = undefined;
PlatformOperationCache.getInstance().update(operation);
operation.onValidCredentialsProvided(message.authenticator);
}
);
}
private getUserInteractionPlatformOperation(
operationId: string,
errorMessage: string
): UserInteractionPlatformOperation {
const operation = PlatformOperationCache.getInstance().read(operationId);
if (operation instanceof UserInteractionPlatformOperation) {
return operation;
}
throw new Error(errorMessage);
}
}