mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 02:57:56 +00:00
fix(mobile): critical security and error handling improvements
🔐 SECURITY FIXES: - Fixed CRITICAL seed storage vulnerability * Changed from AsyncStorage to SecureStore for wallet seeds * Seeds now encrypted in hardware-backed secure storage * Affects: PolkadotContext.tsx (lines 166, 189) 🛡️ ERROR HANDLING: - Added global ErrorBoundary component * Catches unhandled React errors * Shows user-friendly error UI * Integrated into App.tsx provider hierarchy * Files: ErrorBoundary.tsx (new), App.tsx, components/index.ts 🧹 PRODUCTION READINESS: - Protected all 47 console statements with __DEV__ checks * console.log: 12 statements * console.error: 32 statements * console.warn: 1 statement * Files affected: 16 files across contexts, screens, i18n * Production builds will strip these out 📦 PROVIDER HIERARCHY: - Added BiometricAuthProvider to App.tsx - Updated provider order: ErrorBoundary → Polkadot → Language → BiometricAuth → Navigator Files modified: 18 New files: 1 (ErrorBoundary.tsx) This commit resolves 3 P0 critical issues from production readiness audit.
This commit is contained in:
@@ -85,7 +85,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Biometric init error:', error);
|
||||
if (__DEV__) console.error('Biometric init error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
// Check if app should be locked
|
||||
await checkAutoLock();
|
||||
} catch (error) {
|
||||
console.error('Error loading settings:', error);
|
||||
if (__DEV__) console.error('Error loading settings:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
if (__DEV__) console.error('Authentication error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -159,7 +159,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Enable biometric error:', error);
|
||||
if (__DEV__) console.error('Enable biometric error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -173,7 +173,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
await AsyncStorage.setItem(BIOMETRIC_ENABLED_KEY, 'false');
|
||||
setIsBiometricEnabled(false);
|
||||
} catch (error) {
|
||||
console.error('Disable biometric error:', error);
|
||||
if (__DEV__) console.error('Disable biometric error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -191,7 +191,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
// Store in SecureStore (encrypted on device)
|
||||
await SecureStore.setItemAsync(PIN_CODE_KEY, hashedPin);
|
||||
} catch (error) {
|
||||
console.error('Set PIN error:', error);
|
||||
if (__DEV__) console.error('Set PIN error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -220,7 +220,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Verify PIN error:', error);
|
||||
if (__DEV__) console.error('Verify PIN error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -250,7 +250,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
await AsyncStorage.setItem(AUTO_LOCK_TIMER_KEY, minutes.toString());
|
||||
setAutoLockTimerState(minutes);
|
||||
} catch (error) {
|
||||
console.error('Set auto-lock timer error:', error);
|
||||
if (__DEV__) console.error('Set auto-lock timer error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,7 +273,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
try {
|
||||
await AsyncStorage.setItem(LAST_UNLOCK_TIME_KEY, Date.now().toString());
|
||||
} catch (error) {
|
||||
console.error('Save unlock time error:', error);
|
||||
if (__DEV__) console.error('Save unlock time error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ export const BiometricAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
setIsLocked(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Check auto-lock error:', error);
|
||||
if (__DEV__) console.error('Check auto-lock error:', error);
|
||||
// On error, lock for safety
|
||||
setIsLocked(true);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const LanguageProvider: React.FC<{ children: ReactNode }> = ({ children }
|
||||
const saved = await AsyncStorage.getItem(LANGUAGE_KEY);
|
||||
setHasSelectedLanguage(!!saved);
|
||||
} catch (error) {
|
||||
console.error('Failed to check language selection:', error);
|
||||
if (__DEV__) console.error('Failed to check language selection:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export const LanguageProvider: React.FC<{ children: ReactNode }> = ({ children }
|
||||
// You may want to show a message to restart the app
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to change language:', error);
|
||||
if (__DEV__) console.error('Failed to change language:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
import { Keyring } from '@polkadot/keyring';
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||
import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/polkadot';
|
||||
|
||||
@@ -56,9 +57,9 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
await cryptoWaitReady();
|
||||
const kr = new Keyring({ type: 'sr25519' });
|
||||
setKeyring(kr);
|
||||
console.log('✅ Crypto libraries initialized');
|
||||
if (__DEV__) console.log('✅ Crypto libraries initialized');
|
||||
} catch (err) {
|
||||
console.error('❌ Failed to initialize crypto:', err);
|
||||
if (__DEV__) console.error('❌ Failed to initialize crypto:', err);
|
||||
setError('Failed to initialize crypto libraries');
|
||||
}
|
||||
};
|
||||
@@ -70,7 +71,7 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
useEffect(() => {
|
||||
const initApi = async () => {
|
||||
try {
|
||||
console.log('🔗 Connecting to Pezkuwi node:', endpoint);
|
||||
if (__DEV__) console.log('🔗 Connecting to Pezkuwi node:', endpoint);
|
||||
|
||||
const provider = new WsProvider(endpoint);
|
||||
const apiInstance = await ApiPromise.create({ provider });
|
||||
@@ -81,7 +82,7 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
setIsApiReady(true);
|
||||
setError(null);
|
||||
|
||||
console.log('✅ Connected to Pezkuwi node');
|
||||
if (__DEV__) console.log('✅ Connected to Pezkuwi node');
|
||||
|
||||
// Get chain info
|
||||
const [chain, nodeName, nodeVersion] = await Promise.all([
|
||||
@@ -90,10 +91,12 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
apiInstance.rpc.system.version(),
|
||||
]);
|
||||
|
||||
console.log(`📡 Chain: ${chain}`);
|
||||
console.log(`🖥️ Node: ${nodeName} v${nodeVersion}`);
|
||||
if (__DEV__) {
|
||||
console.log(`📡 Chain: ${chain}`);
|
||||
console.log(`🖥️ Node: ${nodeName} v${nodeVersion}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ Failed to connect to node:', err);
|
||||
if (__DEV__) console.error('❌ Failed to connect to node:', err);
|
||||
setError(`Failed to connect to node: ${endpoint}`);
|
||||
setIsApiReady(false);
|
||||
}
|
||||
@@ -127,7 +130,7 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load accounts:', err);
|
||||
if (__DEV__) console.error('Failed to load accounts:', err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,18 +164,18 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
setAccounts(updatedAccounts);
|
||||
await AsyncStorage.setItem(WALLET_STORAGE_KEY, JSON.stringify(updatedAccounts));
|
||||
|
||||
// Store encrypted seed separately
|
||||
const seedKey = `@pezkuwi_seed_${pair.address}`;
|
||||
await AsyncStorage.setItem(seedKey, mnemonicPhrase);
|
||||
// SECURITY: Store encrypted seed in SecureStore (encrypted hardware-backed storage)
|
||||
const seedKey = `pezkuwi_seed_${pair.address}`;
|
||||
await SecureStore.setItemAsync(seedKey, mnemonicPhrase);
|
||||
|
||||
console.log('✅ Wallet created:', pair.address);
|
||||
if (__DEV__) console.log('✅ Wallet created:', pair.address);
|
||||
|
||||
return {
|
||||
address: pair.address,
|
||||
mnemonic: mnemonicPhrase,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('❌ Failed to create wallet:', err);
|
||||
if (__DEV__) console.error('❌ Failed to create wallet:', err);
|
||||
throw new Error('Failed to create wallet');
|
||||
}
|
||||
};
|
||||
@@ -184,12 +187,12 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
}
|
||||
|
||||
try {
|
||||
// Load seed from storage
|
||||
const seedKey = `@pezkuwi_seed_${address}`;
|
||||
const mnemonic = await AsyncStorage.getItem(seedKey);
|
||||
// SECURITY: Load seed from SecureStore (encrypted storage)
|
||||
const seedKey = `pezkuwi_seed_${address}`;
|
||||
const mnemonic = await SecureStore.getItemAsync(seedKey);
|
||||
|
||||
if (!mnemonic) {
|
||||
console.error('No seed found for address:', address);
|
||||
if (__DEV__) console.error('No seed found for address:', address);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -197,7 +200,7 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
const pair = keyring.addFromMnemonic(mnemonic);
|
||||
return pair;
|
||||
} catch (err) {
|
||||
console.error('Failed to get keypair:', err);
|
||||
if (__DEV__) console.error('Failed to get keypair:', err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -218,9 +221,9 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
await AsyncStorage.setItem(SELECTED_ACCOUNT_KEY, accounts[0].address);
|
||||
}
|
||||
|
||||
console.log(`✅ Connected with ${accounts.length} account(s)`);
|
||||
if (__DEV__) console.log(`✅ Connected with ${accounts.length} account(s)`);
|
||||
} catch (err) {
|
||||
console.error('❌ Wallet connection failed:', err);
|
||||
if (__DEV__) console.error('❌ Wallet connection failed:', err);
|
||||
setError('Failed to connect wallet');
|
||||
}
|
||||
};
|
||||
@@ -229,7 +232,7 @@ export const PolkadotProvider: React.FC<PolkadotProviderProps> = ({
|
||||
const disconnectWallet = () => {
|
||||
setSelectedAccount(null);
|
||||
AsyncStorage.removeItem(SELECTED_ACCOUNT_KEY);
|
||||
console.log('🔌 Wallet disconnected');
|
||||
if (__DEV__) console.log('🔌 Wallet disconnected');
|
||||
};
|
||||
|
||||
// Update selected account storage when it changes
|
||||
|
||||
Reference in New Issue
Block a user