Skip to content

Commit

Permalink
NEVISACCESSAPP-5966: added password authenticator with custom policy
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-toth-leeder committed Jul 18, 2024
1 parent 982172a commit 7791359
Show file tree
Hide file tree
Showing 22 changed files with 597 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AuthCloudApiRegistrationScreen from './screens/AuthCloudApiRegistrationSc
import ConfirmationScreen from './screens/ConfirmationScreen';
import DeviceInformationChangeScreen from './screens/DeviceInformationChangeScreen';
import HomeScreen from './screens/HomeScreen';
import PasswordScreen from './screens/PasswordScreen';
import PinScreen from './screens/PinScreen';
import ReadQrCodeScreen from './screens/ReadQrCodeScreen';
import ResultScreen from './screens/ResultScreen';
Expand Down Expand Up @@ -49,6 +50,7 @@ export default function App() {
component={SelectAuthenticatorScreen}
/>
<RootStack.Screen name="Pin" component={PinScreen} />
<RootStack.Screen name="Password" component={PasswordScreen} />
<RootStack.Screen
name="DeviceInformationChange"
component={DeviceInformationChangeScreen}
Expand Down
10 changes: 10 additions & 0 deletions src/error/AppError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export class AppErrorPinAuthenticatorNotFound extends AppError {
}
}

export class AppErrorPasswordAuthenticatorNotFound extends AppError {
description: string = i18next.t('appError.passwordAuthenticatorNotFound');
cause?: string;

constructor(cause?: string) {
super();
this.cause = cause;
}
}

export class AppErrorDeviceInformationNotFound extends AppError {
description: string = i18next.t('appError.deviceInformationNotFound');
cause?: string;
Expand Down
25 changes: 24 additions & 1 deletion src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"deregister": "Deregister",
"deviceInformationChange": "Change Device Information",
"pinChange": "PIN Change",
"passwordChange": "Password Change",
"deleteAuthenticators": "Delete Authenticators",
"inBandRegister": "In-Band Register",
"identitySuiteOnly": "Identity Suite only"
Expand Down Expand Up @@ -53,6 +54,26 @@
"pinProtectionStatusDescriptionLocked": "Authenticator is locked.",
"pinProtectionStatusDescriptionUnlocked": "Authenticator is unlocked. You have {{remainingRetries}} try left.\nPlease retry in {{coolDown}} seconds."
},
"password": {
"enrollment": {
"title": "Create password",
"description": "Please define a password."
},
"verification": {
"title": "Verify password",
"description": "Please enter your password to complete the process."
},
"change": {
"title": "Change password",
"description": "Please define a password."
},
"placeholder": {
"password": "Enter password",
"oldPassword": "Enter old password"
},
"passwordProtectionStatusDescriptionLocked": "Authenticator is locked.",
"passwordProtectionStatusDescriptionUnlocked": "Authenticator is unlocked. You have {{remainingRetries}} try left.\nPlease retry in {{coolDown}} seconds."
},
"transactionConfirmation": {
"title": "Transaction Confirmation"
},
Expand Down Expand Up @@ -91,7 +112,8 @@
"faceID": "Face ID",
"devicePasscode": "Device Passcode",
"fingerprint": "Fingerprint",
"touchID": "Touch ID"
"touchID": "Touch ID",
"password": "Password"
},
"notPolicyCompliant": "This authentication method cannot be used with your application",
"notEnrolled": "Before using the authenticator, enroll it in the phone System Settings"
Expand All @@ -105,6 +127,7 @@
"deviceInformationChange": "Device Information Change",
"payloadDecode": "OOB Payload Decode",
"pinChange": "PIN Change",
"passwordChange": "Password Change",
"localData": "Local Data",
"unknown": "Operation",
"success": {
Expand Down
5 changes: 4 additions & 1 deletion src/model/AuthenticatorItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export class AuthenticatorItem {
this.isPolicyCompliant = isPolicyCompliant;
this.isUserEnrolled = isUserEnrolled;
this.isEnabled =
isPolicyCompliant && (authenticator.aaid === Aaid.PIN.rawValue() || isUserEnrolled);
isPolicyCompliant &&
(authenticator.aaid === Aaid.PIN.rawValue() ||
authenticator.aaid === Aaid.PASSWORD.rawValue() ||
isUserEnrolled);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/model/OperationType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum OperationType {
deviceInformationChange,
payloadDecode,
pinChange,
passwordChange,
localData,
unknown,
}
Expand All @@ -36,6 +37,8 @@ export class OperationTypeUtils {
return i18next.t('operation.payloadDecode');
case OperationType.pinChange:
return i18next.t('operation.pinChange');
case OperationType.passwordChange:
return i18next.t('operation.passwordChange');
case OperationType.localData:
return i18next.t('operation.localData');
case OperationType.unknown:
Expand Down
9 changes: 9 additions & 0 deletions src/model/PasswordMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright © 2024 Nevis Security AG. All rights reserved.
*/

export enum PasswordMode {
enrollment,
verification,
credentialChange,
}
2 changes: 2 additions & 0 deletions src/screens/AuthCloudApiRegistrationViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OperationType } from '../model/OperationType';
import { BiometricUserVerifierImpl } from '../userInteraction/BiometricUserVerifierImpl';
import { DevicePasscodeUserVerifierImpl } from '../userInteraction/DevicePasscodeUserVerifierImpl';
import { FingerprintUserVerifierImpl } from '../userInteraction/FingerprintUserVerifierImpl';
import { PasswordEnrollerImpl } from '../userInteraction/PasswordEnrollerImpl';
import { PinEnrollerImpl } from '../userInteraction/PinEnrollerImpl';
import { RegistrationAuthenticatorSelectorImpl } from '../userInteraction/RegistrationAuthenticatorSelectorImpl';
import { ClientProvider } from '../utility/ClientProvider';
Expand All @@ -30,6 +31,7 @@ const useAuthCloudApiRegistrationViewModel = () => {
.deviceInformation(DeviceInformationUtils.create())
.authenticatorSelector(new RegistrationAuthenticatorSelectorImpl())
.pinEnroller(new PinEnrollerImpl())
.passwordEnroller(new PasswordEnrollerImpl())
.biometricUserVerifier(new BiometricUserVerifierImpl())
.devicePasscodeUserVerifier(new DevicePasscodeUserVerifierImpl())
.fingerprintUserVerifier(new FingerprintUserVerifierImpl())
Expand Down
2 changes: 2 additions & 0 deletions src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const HomeScreen = () => {
changeDeviceInformation,
deleteLocalAuthenticators,
changePin,
changePassword,
} = useHomeViewModel();

const { t } = useTranslation();
Expand Down Expand Up @@ -99,6 +100,7 @@ const HomeScreen = () => {
onPress={changeDeviceInformation}
/>
<OutlinedButton text={t('home.pinChange')} onPress={changePin} />
<OutlinedButton text={t('home.passwordChange')} onPress={changePassword} />
<OutlinedButton
text={t('home.authCloudApiRegistration')}
onPress={authCloudApiRegister}
Expand Down
63 changes: 59 additions & 4 deletions src/screens/HomeViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import { AppEnvironment, ConfigurationLoader } from '../configuration/Configurat
import {
AppErrorAccountsNotFound,
AppErrorDeviceInformationNotFound,
AppErrorPasswordAuthenticatorNotFound,
AppErrorPinAuthenticatorNotFound,
} from '../error/AppError';
import { ErrorHandler } from '../error/ErrorHandler';
import { AccountItem } from '../model/AccountItem';
import { OperationType } from '../model/OperationType';
import * as OutOfBandOperationHandler from '../userInteraction/OutOfBandOperationHandler';
import { PasswordChangerImpl } from '../userInteraction/PasswordChangerImpl';
import { PinChangerImpl } from '../userInteraction/PinChangerImpl';
import { ClientProvider } from '../utility/ClientProvider';
import * as RootNavigation from '../utility/RootNavigation';
Expand Down Expand Up @@ -219,7 +221,9 @@ const useHomeViewModel = () => {
);
}

console.log(`Available device info: ${deviceInformation}`);
console.log(
`Available device info: ${JSON.stringify(deviceInformation, null, ' ')}`
);
navigation.navigate('DeviceInformationChange', {
name: deviceInformation.name,
});
Expand Down Expand Up @@ -284,16 +288,16 @@ const useHomeViewModel = () => {
} else if (eligibleAccounts.length === 1) {
// in case that there is only one account, then we can select it automatically
console.log('Automatically selecting account for PIN Change');
await startPinChange(eligibleAccounts);
await startPinChange(eligibleAccounts.at(0)!);
} else {
// in case that there are multiple eligible accounts then we have to show the account selection screen
return selectAccount(OperationType.pinChange);
}

async function startPinChange(accounts: Array<Account>) {
async function startPinChange(account: Account) {
const client = ClientProvider.getInstance().client;
client?.operations.pinChange
.username(accounts.at(0)!.username)
.username(account.username)
.pinChanger(new PinChangerImpl())
.onSuccess(() => {
console.log('PIN Change succeeded.');
Expand All @@ -307,6 +311,56 @@ const useHomeViewModel = () => {
}
}

async function changePassword() {
// we should only pass the accounts to the account selection that already have a password enrollment
const filteredAuthenticators = localAuthenticators.filter((authenticator) => {
return authenticator.aaid === Aaid.PASSWORD.rawValue();
});
const passwordAuthenticator = filteredAuthenticators.at(0);
if (!passwordAuthenticator) {
return ErrorHandler.handle(
OperationType.passwordChange,
new AppErrorPasswordAuthenticatorNotFound(
'Password change failed, there are no registered password authenticators'
)
);
}

const userEnrollment = passwordAuthenticator.userEnrollment;
const eligibleAccounts = localAccounts.filter((account) => {
return userEnrollment.isEnrolled(account.username);
});
if (eligibleAccounts.length === 0) {
return ErrorHandler.handle(
OperationType.passwordChange,
new AppErrorAccountsNotFound(`Password change failed, no eligible accounts found`)
);
} else if (eligibleAccounts.length === 1) {
// in case that there is only one account, then we can select it automatically
console.log('Automatically selecting account for password Change');
await startPasswordChange(eligibleAccounts.at(0)!);
} else {
// in case that there are multiple eligible accounts then we have to show the account selection screen
return selectAccount(OperationType.passwordChange);
}

async function startPasswordChange(account: Account) {
const client = ClientProvider.getInstance().client;
client?.operations.passwordChange
.username(account.username)
.passwordChanger(new PasswordChangerImpl())
.onSuccess(() => {
console.log('Password Change succeeded.');
RootNavigation.navigate('Result', {
operation: OperationType.passwordChange,
});
})
.onError(ErrorHandler.handle.bind(null, OperationType.passwordChange))
.execute()
.catch(ErrorHandler.handle.bind(null, OperationType.passwordChange));
}
}

return {
numberOfAccounts,
initClient,
Expand All @@ -320,6 +374,7 @@ const useHomeViewModel = () => {
changeDeviceInformation,
deleteLocalAuthenticators,
changePin,
changePassword,
};
};

Expand Down
Loading

0 comments on commit 7791359

Please sign in to comment.