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/machines/QrLoginMachine.typegen.ts b/machines/QrLoginMachine.typegen.ts
index ae58a354c3..95f31f8cb9 100644
--- a/machines/QrLoginMachine.typegen.ts
+++ b/machines/QrLoginMachine.typegen.ts
@@ -1,56 +1,85 @@
+// This file was automatically generated. Edits will be overwritten
- // This file was automatically generated. Edits will be overwritten
-
- export interface Typegen0 {
- '@@xstate/typegen': true;
- internalEvents: {
- "done.invoke.QrLogin.linkTransaction:invocation[0]": { type: "done.invoke.QrLogin.linkTransaction:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
-"done.invoke.QrLogin.sendingAuthenticate:invocation[0]": { type: "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
-"error.platform.QrLogin.linkTransaction:invocation[0]": { type: "error.platform.QrLogin.linkTransaction:invocation[0]"; data: unknown };
-"error.platform.QrLogin.sendingAuthenticate:invocation[0]": { type: "error.platform.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown };
-"error.platform.QrLogin.sendingConsent:invocation[0]": { type: "error.platform.QrLogin.sendingConsent:invocation[0]"; data: unknown };
-"xstate.init": { type: "xstate.init" };
- };
- invokeSrcNameMap: {
- "linkTransaction": "done.invoke.QrLogin.linkTransaction:invocation[0]";
-"sendAuthenticate": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]";
-"sendConsent": "done.invoke.QrLogin.sendingConsent:invocation[0]";
- };
- missingImplementations: {
- actions: never;
- delays: never;
- guards: never;
- services: never;
- };
- eventsCausingActions: {
- "SetErrorMessage": "error.platform.QrLogin.linkTransaction:invocation[0]" | "error.platform.QrLogin.sendingAuthenticate:invocation[0]" | "error.platform.QrLogin.sendingConsent:invocation[0]";
-"expandLinkTransResp": "done.invoke.QrLogin.linkTransaction:invocation[0]";
-"forwardToParent": "DISMISS";
-"loadMyVcs": "done.invoke.QrLogin.linkTransaction:invocation[0]";
-"loadThumbprint": "FACE_VALID";
-"resetLinkTransactionId": "GET";
-"resetSelectedVoluntaryClaims": "GET";
-"setClaims": "done.invoke.QrLogin.linkTransaction:invocation[0]";
-"setConsentClaims": "TOGGLE_CONSENT_CLAIM";
-"setLinkedTransactionId": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]";
-"setMyVcs": "STORE_RESPONSE";
-"setScanData": "GET";
-"setSelectedVc": "SELECT_VC";
-"setThumbprint": "STORE_RESPONSE";
-"setlinkTransactionResponse": "done.invoke.QrLogin.linkTransaction:invocation[0]";
- };
- eventsCausingDelays: {
-
- };
- eventsCausingGuards: {
- "isConsentAlreadyCaptured": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]";
- };
- eventsCausingServices: {
- "linkTransaction": "GET";
-"sendAuthenticate": "STORE_RESPONSE";
-"sendConsent": "CONFIRM";
- };
- matchesStates: "ShowError" | "done" | "faceAuth" | "invalidIdentity" | "linkTransaction" | "loadMyVcs" | "loadingThumbprint" | "requestConsent" | "sendingAuthenticate" | "sendingConsent" | "showvcList" | "success" | "waitingForData";
- tags: never;
- }
-
\ No newline at end of file
+export interface Typegen0 {
+ '@@xstate/typegen': true;
+ internalEvents: {
+ 'done.invoke.QrLogin.linkTransaction:invocation[0]': {
+ type: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ data: unknown;
+ __tip: 'See the XState TS docs to learn how to strongly type this.';
+ };
+ 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]': {
+ type: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
+ data: unknown;
+ __tip: 'See the XState TS docs to learn how to strongly type this.';
+ };
+ 'error.platform.QrLogin.linkTransaction:invocation[0]': {
+ type: 'error.platform.QrLogin.linkTransaction:invocation[0]';
+ data: unknown;
+ };
+ 'error.platform.QrLogin.sendingAuthenticate:invocation[0]': {
+ type: 'error.platform.QrLogin.sendingAuthenticate:invocation[0]';
+ data: unknown;
+ };
+ 'error.platform.QrLogin.sendingConsent:invocation[0]': {
+ type: 'error.platform.QrLogin.sendingConsent:invocation[0]';
+ data: unknown;
+ };
+ 'xstate.init': {type: 'xstate.init'};
+ };
+ invokeSrcNameMap: {
+ linkTransaction: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ sendAuthenticate: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
+ sendConsent: 'done.invoke.QrLogin.sendingConsent:invocation[0]';
+ };
+ missingImplementations: {
+ actions: never;
+ delays: never;
+ guards: never;
+ services: never;
+ };
+ eventsCausingActions: {
+ SetErrorMessage:
+ | 'error.platform.QrLogin.linkTransaction:invocation[0]'
+ | 'error.platform.QrLogin.sendingAuthenticate:invocation[0]'
+ | 'error.platform.QrLogin.sendingConsent:invocation[0]';
+ expandLinkTransResp: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ forwardToParent: 'DISMISS';
+ loadMyVcs: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ loadThumbprint: 'FACE_VALID';
+ resetLinkTransactionId: 'GET';
+ resetSelectedVoluntaryClaims: 'GET';
+ setClaims: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ setConsentClaims: 'TOGGLE_CONSENT_CLAIM';
+ setLinkedTransactionId: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
+ setMyVcs: 'STORE_RESPONSE';
+ setScanData: 'GET';
+ setSelectedVc: 'SELECT_VC';
+ setThumbprint: 'STORE_RESPONSE';
+ setlinkTransactionResponse: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
+ };
+ eventsCausingDelays: {};
+ eventsCausingGuards: {
+ isConsentAlreadyCaptured: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
+ };
+ eventsCausingServices: {
+ linkTransaction: 'GET';
+ sendAuthenticate: 'STORE_RESPONSE';
+ sendConsent: 'CONFIRM';
+ };
+ matchesStates:
+ | 'ShowError'
+ | 'done'
+ | 'faceAuth'
+ | 'invalidIdentity'
+ | 'linkTransaction'
+ | 'loadMyVcs'
+ | 'loadingThumbprint'
+ | 'requestConsent'
+ | 'sendingAuthenticate'
+ | 'sendingConsent'
+ | 'showvcList'
+ | 'success'
+ | 'waitingForData';
+ tags: never;
+}
diff --git a/machines/revoke.typegen.ts b/machines/revoke.typegen.ts
index 02b7d0222c..da3480cde1 100644
--- a/machines/revoke.typegen.ts
+++ b/machines/revoke.typegen.ts
@@ -1,45 +1,62 @@
+// This file was automatically generated. Edits will be overwritten
- // This file was automatically generated. Edits will be overwritten
-
- export interface Typegen0 {
- '@@xstate/typegen': true;
- internalEvents: {
- "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]": { type: "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
-"error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]": { type: "error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; data: unknown };
-"xstate.init": { type: "xstate.init" };
- };
- invokeSrcNameMap: {
- "requestOtp": "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]";
-"requestRevoke": "done.invoke.RevokeVids.requestingRevoke:invocation[0]";
- };
- missingImplementations: {
- actions: never;
- delays: never;
- guards: never;
- services: never;
- };
- eventsCausingActions: {
- "clearOtp": "DISMISS" | "ERROR" | "REVOKE_VCS" | "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]" | "xstate.init";
-"logRevoked": "STORE_RESPONSE";
-"revokeVID": "SUCCESS";
-"setIdBackendError": "error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]";
-"setOtp": "INPUT_OTP";
-"setOtpError": "ERROR";
-"setTransactionId": "DISMISS" | "REVOKE_VCS" | "xstate.init";
-"setVIDs": "REVOKE_VCS";
- };
- eventsCausingDelays: {
-
- };
- eventsCausingGuards: {
-
- };
- eventsCausingServices: {
- "requestOtp": never;
-"requestRevoke": "INPUT_OTP";
- };
- matchesStates: "acceptingOtpInput" | "acceptingVIDs" | "acceptingVIDs.idle" | "acceptingVIDs.requestingOtp" | "idle" | "invalid" | "invalid.backend" | "invalid.otp" | "loggingRevoke" | "requestingRevoke" | "revokingVc" | { "acceptingVIDs"?: "idle" | "requestingOtp";
-"invalid"?: "backend" | "otp"; };
- tags: never;
- }
-
\ No newline at end of file
+export interface Typegen0 {
+ '@@xstate/typegen': true;
+ internalEvents: {
+ 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': {
+ type: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
+ data: unknown;
+ __tip: 'See the XState TS docs to learn how to strongly type this.';
+ };
+ 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': {
+ type: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
+ data: unknown;
+ };
+ 'xstate.init': {type: 'xstate.init'};
+ };
+ invokeSrcNameMap: {
+ requestOtp: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
+ requestRevoke: 'done.invoke.RevokeVids.requestingRevoke:invocation[0]';
+ };
+ missingImplementations: {
+ actions: never;
+ delays: never;
+ guards: never;
+ services: never;
+ };
+ eventsCausingActions: {
+ clearOtp:
+ | 'DISMISS'
+ | 'ERROR'
+ | 'REVOKE_VCS'
+ | 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'
+ | 'xstate.init';
+ logRevoked: 'STORE_RESPONSE';
+ revokeVID: 'SUCCESS';
+ setIdBackendError: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]';
+ setOtp: 'INPUT_OTP';
+ setOtpError: 'ERROR';
+ setTransactionId: 'DISMISS' | 'REVOKE_VCS' | 'xstate.init';
+ setVIDs: 'REVOKE_VCS';
+ };
+ eventsCausingDelays: {};
+ eventsCausingGuards: {};
+ eventsCausingServices: {
+ requestOtp: never;
+ requestRevoke: 'INPUT_OTP';
+ };
+ matchesStates:
+ | 'acceptingOtpInput'
+ | 'acceptingVIDs'
+ | 'acceptingVIDs.idle'
+ | 'acceptingVIDs.requestingOtp'
+ | 'idle'
+ | 'invalid'
+ | 'invalid.backend'
+ | 'invalid.otp'
+ | 'loggingRevoke'
+ | 'requestingRevoke'
+ | 'revokingVc'
+ | {acceptingVIDs?: 'idle' | 'requestingOtp'; invalid?: 'backend' | 'otp'};
+ tags: never;
+}
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 4a99821a80..6cdd49da21 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,
@@ -485,8 +485,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);
@@ -502,7 +512,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/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/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({