diff --git a/App.tsx b/App.tsx
index d4aeaba87b..adc105eef2 100644
--- a/App.tsx
+++ b/App.tsx
@@ -15,7 +15,12 @@ import {
import {DualMessageOverlay} from './components/DualMessageOverlay';
import {useApp} from './screens/AppController';
import {Alert} from 'react-native';
-import {configureTelemetry} from './shared/telemetry/TelemetryUtils';
+import {
+ TelemetryConstants,
+ configureTelemetry,
+ getErrorEventData,
+ sendErrorEvent,
+} from './shared/telemetry/TelemetryUtils';
import {MessageOverlay} from './components/MessageOverlay';
import SecureKeystore from 'react-native-secure-keystore';
import {isHardwareKeystoreExists} from './shared/cryptoutil/cryptoUtil';
@@ -59,6 +64,18 @@ const AppLoadingWrapper: React.FC = () => {
);
const controller = useApp();
const {t} = useTranslation('WelcomeScreen');
+ useEffect(() => {
+ if (isKeyInvalidateError) {
+ configureTelemetry();
+ sendErrorEvent(
+ getErrorEventData(
+ TelemetryConstants.FlowType.appLogin,
+ TelemetryConstants.ErrorId.appWasReset,
+ TelemetryConstants.ErrorMessage.appWasReset,
+ ),
+ );
+ }
+ }, [isKeyInvalidateError]);
return (
<>
diff --git a/locales/ara.json b/locales/ara.json
index ecb09b78d5..ff428a2a78 100644
--- a/locales/ara.json
+++ b/locales/ara.json
@@ -205,12 +205,6 @@
},
"AddVcModal": {
"requestingCredential": "جارٍ طلب بيانات الاعتماد...",
- "confirmationDialog": {
- "title": "هل تريد إلغاء التنزيل؟",
- "message": "بمجرد الإلغاء، لن يتم تنزيل بطاقتك وستحتاج إلى إعادة بدء التنزيل.",
- "wait": "لا، سأنتظر",
- "cancel": "نعم، إلغاء"
- },
"errors": {
"input": {
"empty": "فارغًا",
@@ -275,7 +269,13 @@
"title": "التحقق من OTP",
"otpSentMessage": "لقد أرسلنا الرمز المكون من 6 أرقام إلى رقم هاتفك المحمول المسجل!",
"resendTheCode": "يمكنك إعادة إرسال الرمز بتنسيق ",
- "resendCode": "أعد إرسال الرمز"
+ "resendCode": "أعد إرسال الرمز",
+ "confirmationDialog": {
+ "title": "هل تريد إلغاء التنزيل؟",
+ "message": "بمجرد الإلغاء، لن يتم تنزيل بطاقتك وستحتاج إلى إعادة بدء التنزيل.",
+ "wait": "لا، سأنتظر",
+ "cancel": "نعم، إلغاء"
+ }
},
"MyVcsTab": {
"bringYourDigitalID": "أحضر هويتك الرقمية",
diff --git a/locales/en.json b/locales/en.json
index 244b41fc3f..afa5f55005 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -206,12 +206,6 @@
},
"AddVcModal": {
"requestingCredential": "Requesting credential...",
- "confirmationDialog": {
- "title": "Do you want to cancel downloading?",
- "message": "Once cancelled, your card will not be downloaded and you need to reinitiate the download.",
- "wait": "No, I’ll wait",
- "cancel": "Yes, Cancel"
- },
"errors": {
"input": {
"empty": "The input cannot be empty",
@@ -276,7 +270,13 @@
"title": "OTP Verification",
"otpSentMessage": "We've sent the 6 digit code to your registered mobile number!",
"resendTheCode": "You can resend the code in ",
- "resendCode": "Resend Code"
+ "resendCode": "Resend Code",
+ "confirmationDialog": {
+ "title": "Do you want to cancel downloading?",
+ "message": "Once cancelled, your card will not be downloaded and you need to reinitiate the download.",
+ "wait": "No, I’ll wait",
+ "cancel": "Yes, Cancel"
+ }
},
"MyVcsTab": {
"bringYourDigitalID": "Bring your digital identity",
diff --git a/locales/fil.json b/locales/fil.json
index ee623b7102..f2c351bf67 100644
--- a/locales/fil.json
+++ b/locales/fil.json
@@ -204,12 +204,6 @@
},
"AddVcModal": {
"requestingCredential": "Humihiling ng kredensyal...",
- "confirmationDialog": {
- "title": "Gusto mo bang kanselahin ang pag-download?",
- "message": "Kapag nakansela, hindi na mada-download ang iyong card at kailangan mong simulan muli ang pag-download.",
- "wait": "Hindi, maghihintay ako",
- "cancel": "Oo, Kanselahin"
- },
"errors": {
"input": {
"empty": "Hindi maaaring walang laman ang input",
@@ -274,7 +268,13 @@
"title": "Pag-verify ng OTP",
"otpSentMessage": "Ipinadala namin ang 6 na digit na code sa iyong rehistradong mobile number!",
"resendTheCode": "Maaari mong ipadala muli ang code sa ",
- "resendCode": "Ipadala muli ang Code"
+ "resendCode": "Ipadala muli ang Code",
+ "confirmationDialog": {
+ "title": "Gusto mo bang kanselahin ang pag-download?",
+ "message": "Kapag nakansela, hindi na mada-download ang iyong card at kailangan mong simulan muli ang pag-download.",
+ "wait": "Hindi, maghihintay ako",
+ "cancel": "Oo, Kanselahin"
+ }
},
"MyVcsTab": {
"bringYourDigitalID": "Dalhin ang Iyong Digital ID",
diff --git a/locales/hin.json b/locales/hin.json
index 3232e48675..4679524673 100644
--- a/locales/hin.json
+++ b/locales/hin.json
@@ -202,12 +202,6 @@
},
"AddVcModal": {
"requestingCredential": "क्रेडेंशियल का अनुरोध कर रहा है...",
- "confirmationDialog": {
- "title": "क्या आप डाउनलोडिंग रद्द करना चाहते हैं?",
- "message": "एक बार रद्द होने पर, आपका कार्ड डाउनलोड नहीं किया जाएगा और आपको डाउनलोड फिर से शुरू करना होगा।",
- "wait": "नहीं, मैं इंतजार करूंगा",
- "cancel": "हाँ, रद्द करें"
- },
"errors": {
"input": {
"empty": "इनपुट खाली नहीं हो सकता",
@@ -272,7 +266,13 @@
"title": "ओटीपी सत्यापन",
"otpSentMessage": "हमने आपके पंजीकृत मोबाइल नंबर पर 6 अंकों का कोड भेज दिया है!",
"resendTheCode": "आप कोड को फिर से भेज सकते हैं ",
- "resendCode": "पुन: कोड भेजे"
+ "resendCode": "पुन: कोड भेजे",
+ "confirmationDialog": {
+ "title": "क्या आप डाउनलोडिंग रद्द करना चाहते हैं?",
+ "message": "एक बार रद्द होने पर, आपका कार्ड डाउनलोड नहीं किया जाएगा और आपको डाउनलोड फिर से शुरू करना होगा।",
+ "wait": "नहीं, मैं इंतजार करूंगा",
+ "cancel": "हाँ, रद्द करें"
+ }
},
"MyVcsTab": {
"downloadCard": "डाउनलोड कार्ड",
diff --git a/locales/spa.json b/locales/spa.json
index 577758855b..da59717fa5 100644
--- a/locales/spa.json
+++ b/locales/spa.json
@@ -117,12 +117,6 @@
},
"AddVcModal": {
"requestingCredential": "Solicitando credencial...",
- "confirmationDialog": {
- "title": "Quieres cancelar la descarga?",
- "message": "OUna vez cancelada, su tarjeta no se descargará y deberá reiniciar la descarga.",
- "wait": "No, esperaré",
- "cancel": "Sí, cancelar"
- },
"errors": {
"input": {
"empty": "El campo no puede estar vacío",
@@ -179,7 +173,13 @@
},
"OtpVerificationModal": {
"enterOtp": "Ingresa el código de verificación de 6 dígitos que te hemos enviado",
- "header": "Verificación de OTP"
+ "header": "Verificación de OTP",
+ "confirmationDialog": {
+ "title": "Quieres cancelar la descarga?",
+ "message": "OUna vez cancelada, su tarjeta no se descargará y deberá reiniciar la descarga.",
+ "wait": "No, esperaré",
+ "cancel": "Sí, cancelar"
+ }
},
"MyVcsTab": {
"addVcButton": "Agregar Tarjeta",
diff --git a/locales/tam.json b/locales/tam.json
index d033f08ea8..06333ea85b 100644
--- a/locales/tam.json
+++ b/locales/tam.json
@@ -201,12 +201,6 @@
},
"AddVcModal": {
"requestingCredential": "நற்சான்றிதழைக் கோருகிறது...",
- "confirmationDialog": {
- "title": "பதிவிறக்குவதை ரத்துசெய்ய விரும்புகிறீர்களா?",
- "message": "ரத்துசெய்யப்பட்டதும், உங்கள் கார்டு பதிவிறக்கம் செய்யப்படாது மேலும் நீங்கள் பதிவிறக்கத்தை மீண்டும் தொடங்க வேண்டும்.",
- "wait": "இல்லை, நான் காத்திருப்பேன்",
- "cancel": "ஆம், ரத்துசெய்"
- },
"errors": {
"input": {
"empty": "உள்ளீடு காலியாக இருக்க முடியாது",
@@ -271,7 +265,13 @@
"title": "OTP சரிபார்ப்பு",
"otpSentMessage": "உங்கள் பதிவு செய்யப்பட்ட மொபைல் எண்ணுக்கு 6 இலக்கக் குறியீட்டை அனுப்பியுள்ளோம்!",
"resendTheCode": "நீங்கள் குறியீட்டை மீண்டும் அனுப்பலாம் ",
- "resendCode": "குறியீட்டை மீண்டும் அனுப்பு"
+ "resendCode": "குறியீட்டை மீண்டும் அனுப்பு",
+ "confirmationDialog": {
+ "title": "பதிவிறக்குவதை ரத்துசெய்ய விரும்புகிறீர்களா?",
+ "message": "ரத்துசெய்யப்பட்டதும், உங்கள் கார்டு பதிவிறக்கம் செய்யப்படாது மேலும் நீங்கள் பதிவிறக்கத்தை மீண்டும் தொடங்க வேண்டும்.",
+ "wait": "இல்லை, நான் காத்திருப்பேன்",
+ "cancel": "ஆம், ரத்துசெய்"
+ }
},
"MyVcsTab": {
"bringYourDigitalID": "உங்கள் டிஜிட்டல் ஐடியைக் கொண்டு வாருங்கள்",
diff --git a/machines/settings.ts b/machines/settings.ts
index 5c1559e08d..2a9feee326 100644
--- a/machines/settings.ts
+++ b/machines/settings.ts
@@ -164,8 +164,13 @@ export const settingsMachine = model.createMachine(
}),
updateDefaults: model.assign({
- appId: () => {
- const appId = generateAppId();
+ appId: (_, event) => {
+ const appId =
+ event.response != null &&
+ event.response.encryptedData == null &&
+ event.response.appId != null
+ ? event.response.appId
+ : generateAppId();
__AppId.setValue(appId);
return appId;
},
@@ -246,7 +251,10 @@ export const settingsMachine = model.createMachine(
},
guards: {
- hasData: (_, event) => event.response != null,
+ hasData: (_, event) =>
+ event.response != null &&
+ event.response.encryptedData != null &&
+ event.response.appId != null,
hasPartialData: (_, event) =>
event.response != null && event.response.appId == null,
},
diff --git a/machines/settings.typegen.ts b/machines/settings.typegen.ts
index 31ee28ed65..6534f02f14 100644
--- a/machines/settings.typegen.ts
+++ b/machines/settings.typegen.ts
@@ -18,19 +18,12 @@ export interface Typegen0 {
resetInjiProps: 'done.invoke.settings.resetInjiProps:invocation[0]';
};
missingImplementations: {
- actions: 'injiTourGuide';
+ actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
- injiTourGuide:
- | 'ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS'
- | 'BACK'
- | 'CANCEL'
- | 'STORE_RESPONSE'
- | 'done.invoke.settings.resetInjiProps:invocation[0]'
- | 'error.platform.settings.resetInjiProps:invocation[0]';
requestStoredContext: 'xstate.init';
resetCredentialRegistry: 'CANCEL' | 'UPDATE_MIMOTO_HOST';
setContext: 'STORE_RESPONSE';
@@ -64,7 +57,6 @@ export interface Typegen0 {
matchesStates:
| 'idle'
| 'init'
- | 'injiTourGuide'
| 'resetInjiProps'
| 'showInjiTourGuide'
| 'storingDefaults';
diff --git a/machines/store.ts b/machines/store.ts
index 6d29f5328b..921fbde33f 100644
--- a/machines/store.ts
+++ b/machines/store.ts
@@ -12,7 +12,7 @@ import {
import {createModel} from 'xstate/lib/model';
import {generateSecureRandom} from 'react-native-securerandom';
import {log} from 'xstate/lib/actions';
-import {MY_VCS_STORE_KEY} from '../shared/constants';
+import {MY_VCS_STORE_KEY, SETTINGS_STORE_KEY} from '../shared/constants';
import SecureKeystore from 'react-native-secure-keystore';
import {
AUTH_TIMEOUT,
@@ -486,8 +486,18 @@ export async function setItem(
encryptionKey: string,
) {
try {
- const data = JSON.stringify(value);
- const encryptedData = await encryptJson(encryptionKey, data);
+ let encryptedData;
+ if (key === SETTINGS_STORE_KEY) {
+ const appId = value.appId;
+ delete value.appId;
+ const settings = {
+ encryptedData: await encryptJson(encryptionKey, JSON.stringify(value)),
+ appId,
+ };
+ encryptedData = JSON.stringify(settings);
+ } else {
+ encryptedData = await encryptJson(encryptionKey, JSON.stringify(value));
+ }
await Storage.setItem(key, encryptedData, encryptionKey);
} catch (e) {
console.error('error setItem:', e);
@@ -503,7 +513,19 @@ export async function getItem(
try {
const data = await Storage.getItem(key, encryptionKey);
if (data != null) {
- const decryptedData = await decryptJson(encryptionKey, data);
+ let decryptedData;
+ if (key === SETTINGS_STORE_KEY) {
+ let parsedData = JSON.parse(data);
+ if (parsedData.encryptedData) {
+ decryptedData = await decryptJson(
+ encryptionKey,
+ parsedData.encryptedData,
+ );
+ parsedData.encryptedData = JSON.parse(decryptedData);
+ }
+ return parsedData;
+ }
+ decryptedData = await decryptJson(encryptionKey, data);
return JSON.parse(decryptedData);
}
if (data === null && VCMetadata.isVCKey(key)) {
diff --git a/screens/Home/MyVcs/AddVcModal.tsx b/screens/Home/MyVcs/AddVcModal.tsx
index 1b3eb1f999..adcdfab493 100644
--- a/screens/Home/MyVcs/AddVcModal.tsx
+++ b/screens/Home/MyVcs/AddVcModal.tsx
@@ -6,7 +6,6 @@ import {IdInputModal} from './IdInputModal';
import {useTranslation} from 'react-i18next';
import {GET_INDIVIDUAL_ID} from '../../../shared/constants';
import {TelemetryConstants} from '../../../shared/telemetry/TelemetryUtils';
-import {Button, Column} from '../../../components/ui';
export const AddVcModal: React.FC = props => {
const {t} = useTranslation('AddVcModal');
@@ -16,10 +15,7 @@ export const AddVcModal: React.FC = props => {
if (controller.isRequestingCredential) {
GET_INDIVIDUAL_ID({id: '', idType: 'UIN'});
}
- return (
- (!controller.isAcceptingOtpInput && !controller.isRequestingCredential) ||
- !controller.isDownloadCancelled
- );
+ return controller.isAcceptingUinInput;
};
const dismissIdInputModal = () => {
@@ -38,6 +34,7 @@ export const AddVcModal: React.FC = props => {
{(controller.isAcceptingOtpInput || controller.isDownloadCancelled) && (
= props => {
title={t('requestingCredential')}
progress
/>
-
-
-
-
-
-
-
);
};
diff --git a/screens/Home/MyVcs/OtpVerificationModal.tsx b/screens/Home/MyVcs/OtpVerificationModal.tsx
index b6af9b838b..e5b014c644 100644
--- a/screens/Home/MyVcs/OtpVerificationModal.tsx
+++ b/screens/Home/MyVcs/OtpVerificationModal.tsx
@@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {PinInput} from '../../../components/PinInput';
-import {Column, Text} from '../../../components/ui';
+import {Button, Column, Text} from '../../../components/ui';
import {ModalProps, Modal} from '../../../components/ui/Modal';
import {Theme} from '../../../components/ui/styleUtils';
import {Image, TouchableOpacity} from 'react-native';
@@ -12,6 +12,11 @@ import {
resetRetryCount,
sendImpressionEvent,
} from '../../../shared/telemetry/TelemetryUtils';
+import {MessageOverlay} from '../../../components/MessageOverlay';
+import {
+ OtpVerificationModalProps,
+ useOtpVerificationModal,
+} from './OtpVerificationModalController';
export const OtpVerificationModal: React.FC<
OtpVerificationModalProps
@@ -20,6 +25,8 @@ export const OtpVerificationModal: React.FC<
const [timer, setTimer] = useState(180); // 30 seconds
+ const controller = useOtpVerificationModal(props);
+
useEffect(() => {
sendImpressionEvent(
getImpressionEventData(
@@ -124,13 +131,25 @@ export const OtpVerificationModal: React.FC<
+
+
+
+
+
+
);
};
-
-interface OtpVerificationModalProps extends ModalProps {
- onInputDone: (otp: string) => void;
- error?: string;
- resend?: () => void;
- flow: string;
-}
diff --git a/screens/Home/MyVcs/OtpVerificationModalController.ts b/screens/Home/MyVcs/OtpVerificationModalController.ts
new file mode 100644
index 0000000000..41897d1990
--- /dev/null
+++ b/screens/Home/MyVcs/OtpVerificationModalController.ts
@@ -0,0 +1,26 @@
+import {
+ AddVcModalEvents,
+ AddVcModalMachine,
+ selectIsCancellingDownload,
+} from './AddVcModalMachine';
+import {ActorRefFrom} from 'xstate';
+import {ModalProps} from '../../../components/ui/Modal';
+import {useSelector} from '@xstate/react';
+
+export function useOtpVerificationModal({service}: OtpVerificationModalProps) {
+ return {
+ isDownloadCancelled: useSelector(service, selectIsCancellingDownload),
+
+ WAIT: () => service.send(AddVcModalEvents.WAIT()),
+
+ CANCEL: () => service.send(AddVcModalEvents.CANCEL()),
+ };
+}
+
+export interface OtpVerificationModalProps extends ModalProps {
+ service: ActorRefFrom;
+ onInputDone: (otp: string) => void;
+ error?: string;
+ resend?: () => void;
+ flow: string;
+}
diff --git a/screens/Home/MyVcsTab.tsx b/screens/Home/MyVcsTab.tsx
index b40be95c15..ae4ec54734 100644
--- a/screens/Home/MyVcsTab.tsx
+++ b/screens/Home/MyVcsTab.tsx
@@ -61,7 +61,21 @@ export const MyVcsTab: React.FC = props => {
),
);
}
- }, [controller.areAllVcsLoaded, controller.inProgressVcDownloads]);
+
+ if (controller.isTampered) {
+ sendErrorEvent(
+ getErrorEventData(
+ TelemetryConstants.FlowType.appLogin,
+ TelemetryConstants.ErrorId.vcsAreTampered,
+ TelemetryConstants.ErrorMessage.vcsAreTampered,
+ ),
+ );
+ }
+ }, [
+ controller.areAllVcsLoaded,
+ controller.inProgressVcDownloads,
+ controller.isTampered,
+ ]);
let failedVCsList = [];
controller.downloadFailedVcs.forEach(vc => {
diff --git a/screens/Settings/SettingScreenController.ts b/screens/Settings/SettingScreenController.ts
index 180bcc295c..8a1be82fea 100644
--- a/screens/Settings/SettingScreenController.ts
+++ b/screens/Settings/SettingScreenController.ts
@@ -162,14 +162,10 @@ export function useSettingsScreen(props: RootRouteProps & RequestRouteProps) {
LOGOUT: () => {
setIsVisible(false);
const navigate = () => {
+ props.navigation.navigate('Welcome');
authService.send(AuthEvents.LOGOUT());
};
-
- if (isIOS()) {
- setTimeout(() => navigate(), 0);
- } else {
- navigate();
- }
+ setTimeout(() => navigate(), 10);
},
CANCEL: () => {
diff --git a/shared/storage.ts b/shared/storage.ts
index 1dec2fc15d..1d3696759f 100644
--- a/shared/storage.ts
+++ b/shared/storage.ts
@@ -15,8 +15,14 @@ import {
} from './cryptoutil/cryptoUtil';
import {VCMetadata} from './VCMetadata';
import {ENOENT, getItem} from '../machines/store';
-import {isAndroid, MY_VCS_STORE_KEY, RECEIVED_VCS_STORE_KEY} from './constants';
+import {
+ isAndroid,
+ MY_VCS_STORE_KEY,
+ RECEIVED_VCS_STORE_KEY,
+ SETTINGS_STORE_KEY,
+} from './constants';
import FileStorage, {getFilePath, vcDirectoryPath} from './fileStorage';
+import {__AppId} from './GlobalVariables';
export const MMKV = new MMKVLoader().initialize();
@@ -184,7 +190,11 @@ class Storage {
try {
(await FileStorage.exists(`${vcDirectoryPath}`)) &&
(await FileStorage.removeItem(`${vcDirectoryPath}`));
+ const settings = await MMKV.getItem(SETTINGS_STORE_KEY);
+ const appId = JSON.parse(settings).appId;
+ __AppId.setValue(appId);
MMKV.clearStore();
+ await MMKV.setItem(SETTINGS_STORE_KEY, JSON.stringify({appId: appId}));
} catch (e) {
console.log('Error Occurred while Clearing Storage.', e);
}
diff --git a/shared/telemetry/TelemetryUtils.js b/shared/telemetry/TelemetryUtils.js
index be51421832..3a64ba69b5 100644
--- a/shared/telemetry/TelemetryUtils.js
+++ b/shared/telemetry/TelemetryUtils.js
@@ -210,6 +210,10 @@ export const TelemetryConstants = {
hardwareKeyStore:
'Some security features will be unavailable as hardware key store is not available',
activationCancelled: 'Activation Cancelled',
+ appWasReset:
+ 'Due to the fingerprint / facial recognition update, app security was impacted, and downloaded cards were removed. Please download again',
+ vcsAreTampered:
+ 'Tampered cards detected and removed for security reasons. Please download again',
}),
ErrorId: Object.freeze({
@@ -218,6 +222,8 @@ export const TelemetryConstants = {
userCancel: 'USER_CANCEL',
resend: 'RESEND',
activationFailed: 'ACTIVATION_FAILED',
+ appWasReset: 'APP_WAS_RESET',
+ vcsAreTampered: 'VCS_ARE_TAMPERED',
}),
Screens: Object.freeze({