Skip to content

Commit

Permalink
Merge pull request #2687 from kaloudis/zeus-2683
Browse files Browse the repository at this point in the history
ZEUS-2683: Storage: migrate from `react-native-encrypted-storage` to `react-native-keychain`
  • Loading branch information
kaloudis authored Jan 25, 2025
2 parents 237b2a1 + b217c29 commit 28671f1
Show file tree
Hide file tree
Showing 35 changed files with 1,353 additions and 562 deletions.
50 changes: 22 additions & 28 deletions backends/LNC/credentialStore.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import EncryptedStorage from 'react-native-encrypted-storage';
import Storage from '../../storage';
import { CredentialStore } from '../../zeus_modules/@lightninglabs/lnc-rn';
import hashjs from 'hash.js';

const STORAGE_KEY = 'lnc-rn';
const LNC_STORAGE_KEY = 'lnc-rn';

const hash = (stringToHash: string) =>
hashjs.sha256().update(stringToHash).digest('hex');

/**
* A wrapper around `EncryptedStorage` used to store sensitive data required
* A wrapper around `Storage` used to store sensitive data required
* by LNC to reconnect after the initial pairing process has been completed.
*/
export default class LncCredentialStore implements CredentialStore {
// the data to store in EncryptedStorage
// the data to store in Storage
private persisted = {
serverHost: '',
localKey: '',
Expand Down Expand Up @@ -44,28 +44,22 @@ export default class LncCredentialStore implements CredentialStore {

private async _migrateServerHost() {
try {
const baseKey = `${STORAGE_KEY}:${hash(this._pairingPhrase)}`;
const baseKey = `${LNC_STORAGE_KEY}:${hash(this._pairingPhrase)}`;
const hostKey = `${baseKey}:host`;
const hasNewFormat = await EncryptedStorage.getItem(hostKey);
const hasNewFormat = await Storage.getItem(hostKey);

// Only migrate if new format doesn't exist yet
if (!hasNewFormat) {
const oldData = await EncryptedStorage.getItem(baseKey);
const oldData = await Storage.getItem(baseKey);

if (oldData) {
const parsed = JSON.parse(oldData);
if (parsed.serverHost) {
await EncryptedStorage.setItem(
hostKey,
parsed.serverHost
);
await Storage.setItem(hostKey, parsed.serverHost);

// Remove serverHost from old format
delete parsed.serverHost;
await EncryptedStorage.setItem(
baseKey,
JSON.stringify(parsed)
);
await Storage.setItem(baseKey, parsed);
}
}
}
Expand Down Expand Up @@ -136,9 +130,9 @@ export default class LncCredentialStore implements CredentialStore {

/** Clears any persisted data in the store */
clear() {
const baseKey = `${STORAGE_KEY}:${hash(this._pairingPhrase)}`;
EncryptedStorage.removeItem(baseKey);
EncryptedStorage.removeItem(`${baseKey}:host`);
const baseKey = `${LNC_STORAGE_KEY}:${hash(this._pairingPhrase)}`;
Storage.removeItem(baseKey);
Storage.removeItem(`${baseKey}:host`);
this.persisted = {
serverHost: this.persisted.serverHost,
localKey: '',
Expand All @@ -150,15 +144,15 @@ export default class LncCredentialStore implements CredentialStore {
this._pairingPhrase = '';
}

/** Loads persisted data from EncryptedStorage */
/** Loads persisted data from Storage */
async load(pairingPhrase?: string) {
// only load if pairingPhrase is set
if (!pairingPhrase) return;
try {
const baseKey = `${STORAGE_KEY}:${hash(pairingPhrase)}`;
const baseKey = `${LNC_STORAGE_KEY}:${hash(pairingPhrase)}`;
const hostKey = `${baseKey}:host`;
const json = await EncryptedStorage.getItem(baseKey);
const serverHost = await EncryptedStorage.getItem(hostKey);
const json = await Storage.getItem(baseKey);
const serverHost = await Storage.getItem(hostKey);
if (json) {
this.persisted = JSON.parse(json);
if (serverHost) {
Expand All @@ -179,18 +173,18 @@ export default class LncCredentialStore implements CredentialStore {
//

private _saveServerHost() {
const hostKey = `${STORAGE_KEY}:${hash(this._pairingPhrase)}:host`;
EncryptedStorage.setItem(hostKey, this.persisted.serverHost);
const hostKey = `${LNC_STORAGE_KEY}:${hash(this._pairingPhrase)}:host`;
Storage.setItem(hostKey, this.persisted.serverHost);
}

/** Saves persisted data to EncryptedStorage */
/** Saves persisted data to Storage */
private _save() {
// only save if localKey and remoteKey is set
if (!this._localKey) return;
if (!this._remoteKey) return;
const baseKey = `${STORAGE_KEY}:${hash(this._pairingPhrase)}`;
EncryptedStorage.setItem(baseKey, JSON.stringify(this.persisted));
const baseKey = `${LNC_STORAGE_KEY}:${hash(this._pairingPhrase)}`;
Storage.setItem(baseKey, this.persisted);
}
}

export { STORAGE_KEY, hash };
export { LNC_STORAGE_KEY, hash };
27 changes: 23 additions & 4 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1258,8 +1258,27 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNKeychain (8.1.1):
- RNKeychain (9.2.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNReactNativeHapticFeedback (2.2.0):
- React-Core
- RNReanimated (3.12.0):
Expand Down Expand Up @@ -1312,7 +1331,7 @@ PODS:
- RNVectorIcons (7.1.0):
- React
- SocketRocket (0.7.0)
- SwiftProtobuf (1.26.0)
- SwiftProtobuf (1.28.2)
- TcpSockets (4.0.0):
- React
- VisionCamera (4.3.2):
Expand Down Expand Up @@ -1687,15 +1706,15 @@ SPEC CHECKSUMS:
RNDeviceInfo: 900bd20e1fd3bfd894e7384cc4a83880c0341bd3
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
RNGestureHandler: 326e35460fb6c8c64a435d5d739bea90d7ed4e49
RNKeychain: 3a8b12f0fdd0b967811a48d9e83d582fc2e483eb
RNKeychain: c339dd4a9f13baac6cf6b617bfad3a0eab5df8b1
RNReactNativeHapticFeedback: a6fb5b7a981683bf58af43e3fb827d4b7ed87f83
RNReanimated: f1a8e928e0a5e8698c354c226d8d5f80629f8658
RNScreens: a2d8a2555b4653d7a19706eb172f855657ac30d7
RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc
RNSVG: e77adf5edb2302f0f10dd03a09e92bb9420d914e
RNVectorIcons: 7159ba77d44e14808dcc50fbc03eb5666b1034fc
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3
SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d
TcpSockets: c5f030d4dbc758c4c7acb00496dc92c412b02cd2
VisionCamera: 6ad4b86176f6fea45e90e380d3d530bc5e1abff9
Yoga: ae3c32c514802d30f687a04a6a35b348506d411f
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"react-native-haptic-feedback": "2.2.0",
"react-native-hce": "0.1.2",
"react-native-image-picker": "5.0.1",
"react-native-keychain": "8.1.1",
"react-native-keychain": "9.2.2",
"react-native-level-fs": "3.0.1",
"react-native-linear-gradient": "2.6.2",
"react-native-nfc-manager": "3.14.14",
Expand Down
28 changes: 28 additions & 0 deletions storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Keychain from 'react-native-keychain';

class Storage {
getItem = async (key: string) => {
const response: any = await Keychain.getInternetCredentials(key);
if (response.password) {
return response.password;
}
return false;
};

setItem = async (key: string, value: any) => {
const response = await Keychain.setInternetCredentials(
key,
key,
typeof value === 'string' ? value : JSON.stringify(value)
);
return response;
};

removeItem = async (key: string) => {
const response = await Keychain.resetInternetCredentials(key);
return response;
};
}

const storage = new Storage();
export default storage;
20 changes: 8 additions & 12 deletions stores/ActivityStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { action, observable } from 'mobx';
import EncryptedStorage from 'react-native-encrypted-storage';

// LN
import Payment from './../models/Payment';
Expand All @@ -15,7 +14,10 @@ import TransactionsStore from './TransactionsStore';
import BackendUtils from './../utils/BackendUtils';
import ActivityFilterUtils from '../utils/ActivityFilterUtils';

const STORAGE_KEY = 'zeus-activity-filters';
import Storage from '../storage';

export const LEGACY_ACTIVITY_FILTERS_KEY = 'zeus-activity-filters';
export const ACTIVITY_FILTERS_KEY = 'zeus-activity-filters-v2';

export interface Filter {
[index: string]: any;
Expand Down Expand Up @@ -78,10 +80,7 @@ export default class ActivityStore {
@action
public resetFilters = async () => {
this.filters = DEFAULT_FILTERS;
await EncryptedStorage.setItem(
STORAGE_KEY,
JSON.stringify(this.filters)
);
await Storage.setItem(ACTIVITY_FILTERS_KEY, this.filters);
this.setFilters(this.filters);
};

Expand All @@ -101,10 +100,7 @@ export default class ActivityStore {
startDate: undefined,
endDate: undefined
};
await EncryptedStorage.setItem(
STORAGE_KEY,
JSON.stringify(this.filters)
);
await Storage.setItem(ACTIVITY_FILTERS_KEY, this.filters);
};

@action
Expand Down Expand Up @@ -192,7 +188,7 @@ export default class ActivityStore {
public async getFilters() {
this.loading = true;
try {
const filters = await EncryptedStorage.getItem(STORAGE_KEY);
const filters = await Storage.getItem(ACTIVITY_FILTERS_KEY);
if (filters) {
this.filters = JSON.parse(filters, (key, value) =>
(key === 'startDate' || key === 'endDate') && value
Expand Down Expand Up @@ -224,7 +220,7 @@ export default class ActivityStore {
activity.determineFormattedRemainingTimeUntilExpiry(locale);
}
});
await EncryptedStorage.setItem(STORAGE_KEY, JSON.stringify(filters));
await Storage.setItem(ACTIVITY_FILTERS_KEY, filters);
this.loading = false;
};

Expand Down
22 changes: 12 additions & 10 deletions stores/ChannelBackupStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { action, observable } from 'mobx';
import ReactNativeBlobUtil from 'react-native-blob-util';
import EncryptedStorage from 'react-native-encrypted-storage';
import * as CryptoJS from 'crypto-js';

import NodeInfoStore from './NodeInfoStore';
Expand All @@ -18,8 +17,16 @@ import { LndMobileEventEmitter } from '../utils/LndMobileUtils';
import Base64Utils from '../utils/Base64Utils';
import { errorToUserFriendly } from '../utils/ErrorUtils';

import Storage from '../storage';

const BACKUPS_HOST = 'https://backups.lnolymp.us';

export const LEGACY_LAST_CHANNEL_BACKUP_STATUS = 'LAST_CHANNEL_BACKUP_STATUS';
export const LEGACY_LAST_CHANNEL_BACKUP_TIME = 'LAST_CHANNEL_BACKUP_TIME';

export const LAST_CHANNEL_BACKUP_STATUS = 'zeus-last-channel-backup-status';
export const LAST_CHANNEL_BACKUP_TIME = 'zeus-last-channel-backup-time';

export default class ChannelBackupStore {
@observable public channelEventsSubscription: any;
@observable public backups: Array<any> = [];
Expand All @@ -44,11 +51,8 @@ export default class ChannelBackupStore {
};

logBackupStatus = async (status: string) => {
await EncryptedStorage.setItem('LAST_CHANNEL_BACKUP_STATUS', status);
await EncryptedStorage.setItem(
'LAST_CHANNEL_BACKUP_TIME',
`${new Date()}`
);
await Storage.setItem('LAST_CHANNEL_BACKUP_STATUS', status);
await Storage.setItem('LAST_CHANNEL_BACKUP_TIME', `${new Date()}`);
};

@action
Expand Down Expand Up @@ -317,10 +321,8 @@ export default class ChannelBackupStore {
public initSubscribeChannelEvents = async () => {
// Check if latest channel backup status is success
// or if it's over three days ago and trigger backup
const status = await EncryptedStorage.getItem(
'LAST_CHANNEL_BACKUP_STATUS'
);
const time = await EncryptedStorage.getItem('LAST_CHANNEL_BACKUP_TIME');
const status = await Storage.getItem(LAST_CHANNEL_BACKUP_STATUS);
const time = await Storage.getItem(LAST_CHANNEL_BACKUP_TIME);
if (status && status === 'ERROR') this.backupChannels();
if (time) {
const ONE_HOUR = 60 * 60 * 1000; /* ms */
Expand Down
Loading

0 comments on commit 28671f1

Please # to comment.