From 27de9b329c81bd1a42c155849616969bfb06c83c Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 21 Jul 2021 14:58:32 +0800 Subject: [PATCH 1/3] PersistentWallet context to use SecureStore --- app/api/secure_store.ts | 32 ++++++++++++++++++++++++++++++ app/api/wallet/persistence.test.ts | 6 +++--- package-lock.json | 11 ++++++++++ package.json | 1 + 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 app/api/secure_store.ts diff --git a/app/api/secure_store.ts b/app/api/secure_store.ts new file mode 100644 index 0000000000..4388ea7a04 --- /dev/null +++ b/app/api/secure_store.ts @@ -0,0 +1,32 @@ +import SecureStore from 'expo-secure-store' +import { getEnvironment } from '../environment' +import { getNetwork } from './storage' + +async function getKey (key: string): Promise { + const env = getEnvironment() + const network = await getNetwork() + return `${env.name}.${network}.${key}` +} + +/** + * @param key {string} of item with 'environment' and 'network' prefixed + * @return {string | null} + */ +export async function getItem (key: string): Promise { + return await SecureStore.getItemAsync(await getKey(key)) +} + +/** + * @param key {string} of item with 'environment' and 'network' prefixed + * @param value {string} to set + */ +export async function setItem (key: string, value: string): Promise { + return await SecureStore.setItemAsync(await getKey(key), value) +} + +/** + * @param key {string} of item with 'environment' and 'network' prefixed + */ +export async function removeItem (key: string): Promise { + await SecureStore.deleteItemAsync(await getKey(key)) +} diff --git a/app/api/wallet/persistence.test.ts b/app/api/wallet/persistence.test.ts index ebbf4b1336..4589a34f87 100644 --- a/app/api/wallet/persistence.test.ts +++ b/app/api/wallet/persistence.test.ts @@ -1,9 +1,9 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; +import SecureStore from 'expo-secure-store'; import { EnvironmentNetwork } from "../../environment"; import { WalletPersistence, WalletType } from "./persistence"; -const getItem = jest.spyOn(AsyncStorage, 'getItem') -const setItem = jest.spyOn(AsyncStorage, 'setItem') +const getItem = jest.spyOn(SecureStore, 'getItemAsync') +const setItem = jest.spyOn(SecureStore, 'setItemAsync') beforeEach(() => { jest.clearAllMocks() diff --git a/package-lock.json b/package-lock.json index 08b99239a7..9443bf2f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "expo-linking": "~2.3.1", "expo-localization": "~10.2.0", "expo-random": "~11.2.0", + "expo-secure-store": "^10.2.0", "expo-splash-screen": "~0.11.2", "expo-status-bar": "~1.0.4", "expo-updates": "~0.8.1", @@ -16054,6 +16055,11 @@ "base64-js": "^1.3.0" } }, + "node_modules/expo-secure-store": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-10.2.0.tgz", + "integrity": "sha512-yNahMY3qzEotAYdsE02ps4yGfDay2twasHfsI/7gJB9SrwXYFx5bJuCDk8uTo8jsm6psvDjO+9VMM2DSPHik2A==" + }, "node_modules/expo-splash-screen": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.11.2.tgz", @@ -51799,6 +51805,11 @@ "base64-js": "^1.3.0" } }, + "expo-secure-store": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-10.2.0.tgz", + "integrity": "sha512-yNahMY3qzEotAYdsE02ps4yGfDay2twasHfsI/7gJB9SrwXYFx5bJuCDk8uTo8jsm6psvDjO+9VMM2DSPHik2A==" + }, "expo-splash-screen": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.11.2.tgz", diff --git a/package.json b/package.json index f3619dd66b..6e11b5858b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "expo-linking": "~2.3.1", "expo-localization": "~10.2.0", "expo-random": "~11.2.0", + "expo-secure-store": "^10.2.0", "expo-splash-screen": "~0.11.2", "expo-status-bar": "~1.0.4", "expo-updates": "~0.8.1", From d5a0e882096d08e8d2462069c4bd25cda1dcddb2 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 21 Jul 2021 15:56:16 +0800 Subject: [PATCH 2/3] mock native secure storage (no web variant) --- app/api/wallet/persistence.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/api/wallet/persistence.test.ts b/app/api/wallet/persistence.test.ts index 4589a34f87..324c9d9a31 100644 --- a/app/api/wallet/persistence.test.ts +++ b/app/api/wallet/persistence.test.ts @@ -1,9 +1,15 @@ import SecureStore from 'expo-secure-store'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import { EnvironmentNetwork } from "../../environment"; import { WalletPersistence, WalletType } from "./persistence"; -const getItem = jest.spyOn(SecureStore, 'getItemAsync') -const setItem = jest.spyOn(SecureStore, 'setItemAsync') +jest.mock('expo-secure-store', () => ({ + getItemAsync: jest.fn().mockReturnValue((k) => ''), + setItemAsync: jest.fn().mockReturnValue(new Promise(res => res(''))) +})) + +const getItem = jest.spyOn(AsyncStorage, 'getItem') +const setItem = jest.spyOn(AsyncStorage, 'setItem') beforeEach(() => { jest.clearAllMocks() From d1951eaf71b843441be307c699cd73f26d9c22f1 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 22 Jul 2021 11:50:10 +0800 Subject: [PATCH 3/3] remove platform.select in code, create different platform extension for secure store --- app/api/secure_store.native.ts | 31 ++++++++++++++++++ app/api/secure_store.ts | 50 ++++++++++++++++++------------ app/api/wallet/persistence.test.ts | 8 ++--- app/api/wallet/persistence.ts | 7 +++-- 4 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 app/api/secure_store.native.ts diff --git a/app/api/secure_store.native.ts b/app/api/secure_store.native.ts new file mode 100644 index 0000000000..25508024f1 --- /dev/null +++ b/app/api/secure_store.native.ts @@ -0,0 +1,31 @@ +import { Platform } from 'react-native' +import ExpoSecureStore from 'expo-secure-store' +import { ISecureStore, getKey } from './secure_store' + +class NativeSecureStore implements ISecureStore { + /** + * @param key {string} of item with 'environment' and 'network' prefixed + * @return {string | null} + */ + async getItem (key: string): Promise { + return await ExpoSecureStore.getItemAsync(await getKey(key)) + } + + /** + * @param key {string} of item with 'environment' and 'network' prefixed + * @param value {string} to set + */ + async setItem (key: string, value: string): Promise { + return await ExpoSecureStore.setItemAsync(await getKey(key), value) + } + + /** + * @param key {string} of item with 'environment' and 'network' prefixed + */ + async removeItem (key: string): Promise { + await ExpoSecureStore.deleteItemAsync(await getKey(key)) + } +} + +console.log(Platform.OS) +export const SecureStorage = new NativeSecureStore() diff --git a/app/api/secure_store.ts b/app/api/secure_store.ts index 4388ea7a04..e12a4d9e0f 100644 --- a/app/api/secure_store.ts +++ b/app/api/secure_store.ts @@ -1,32 +1,42 @@ -import SecureStore from 'expo-secure-store' import { getEnvironment } from '../environment' import { getNetwork } from './storage' +import AsyncStorage from '@react-native-async-storage/async-storage' -async function getKey (key: string): Promise { +export async function getKey (key: string): Promise { const env = getEnvironment() const network = await getNetwork() return `${env.name}.${network}.${key}` } -/** - * @param key {string} of item with 'environment' and 'network' prefixed - * @return {string | null} - */ -export async function getItem (key: string): Promise { - return await SecureStore.getItemAsync(await getKey(key)) +export interface ISecureStore { + getItem: (key: string) => Promise + setItem: (key: string, value: string) => Promise + removeItem: (key: string) => Promise } -/** - * @param key {string} of item with 'environment' and 'network' prefixed - * @param value {string} to set - */ -export async function setItem (key: string, value: string): Promise { - return await SecureStore.setItemAsync(await getKey(key), value) -} +class WebAsyncStorage implements ISecureStore { + /** + * @param key {string} of item with 'environment' and 'network' prefixed + * @return {string | null} + */ + async getItem (key: string): Promise { + return await AsyncStorage.getItem(await getKey(key)) + } + + /** + * @param key {string} of item with 'environment' and 'network' prefixed + * @param value {string} to set + */ + async setItem (key: string, value: string): Promise { + return await AsyncStorage.setItem(await getKey(key), value) + } -/** - * @param key {string} of item with 'environment' and 'network' prefixed - */ -export async function removeItem (key: string): Promise { - await SecureStore.deleteItemAsync(await getKey(key)) + /** + * @param key {string} of item with 'environment' and 'network' prefixed + */ + async removeItem (key: string): Promise { + await AsyncStorage.removeItem(await getKey(key)) + } } + +export const SecureStorage = new WebAsyncStorage() diff --git a/app/api/wallet/persistence.test.ts b/app/api/wallet/persistence.test.ts index 324c9d9a31..a57fd2fb59 100644 --- a/app/api/wallet/persistence.test.ts +++ b/app/api/wallet/persistence.test.ts @@ -1,13 +1,13 @@ -import SecureStore from 'expo-secure-store'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { EnvironmentNetwork } from "../../environment"; import { WalletPersistence, WalletType } from "./persistence"; -jest.mock('expo-secure-store', () => ({ - getItemAsync: jest.fn().mockReturnValue((k) => ''), - setItemAsync: jest.fn().mockReturnValue(new Promise(res => res(''))) +jest.mock('react-native/Libraries/Utilities/Platform', () => ({ + OS: 'web', + select: opts => opts.web })) +// web has no SecureStore implementation, fallback to AsyncStorage const getItem = jest.spyOn(AsyncStorage, 'getItem') const setItem = jest.spyOn(AsyncStorage, 'setItem') diff --git a/app/api/wallet/persistence.ts b/app/api/wallet/persistence.ts index bfac342e25..d1584a1ecf 100644 --- a/app/api/wallet/persistence.ts +++ b/app/api/wallet/persistence.ts @@ -1,4 +1,5 @@ -import { getItem, setItem } from '../storage' +// import { getItem, setItem } from '../storage' +import { SecureStorage } from '../secure_store' const KEY = 'WALLET' @@ -18,7 +19,7 @@ export interface WalletData { } async function get (): Promise { - const json = await getItem(KEY) + const json = await SecureStorage.getItem(KEY) if (json !== null) { return JSON.parse(json) } @@ -27,7 +28,7 @@ async function get (): Promise { } async function set (wallets: WalletData[]): Promise { - await setItem(KEY, JSON.stringify(wallets)) + await SecureStorage.setItem(KEY, JSON.stringify(wallets)) } async function add (data: WalletData): Promise {