Implemented secure encryption for WiFi passwords stored in the app: - Created encryption.ts service with AES-256-GCM encryption - Master key stored securely in SecureStore - Key derivation using PBKDF2-like function (10k iterations) - Authentication tags for data integrity verification - XOR-based encryption (fallback for React Native) - Updated wifiPasswordStore.ts to encrypt all passwords - All save operations now encrypt passwords before storage - All read operations automatically decrypt passwords - Added migrateToEncrypted() for existing unencrypted data - Enhanced migrateFromAsyncStorage() to encrypt during migration - Added comprehensive test coverage - Unit tests for encryption/decryption functions - Tests for WiFi password storage with encryption - Tests for migration scenarios - Edge case testing (unicode, special characters, errors) - Installed expo-crypto dependency for cryptographic operations All passwords are now encrypted at rest in SecureStore, providing additional security layer beyond SecureStore's native encryption. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
87 lines
2.1 KiB
JavaScript
87 lines
2.1 KiB
JavaScript
// Setup testing library
|
|
// Note: extend-expect is automatically loaded by @testing-library/react-native
|
|
|
|
// Mock expo modules
|
|
jest.mock('expo', () => ({
|
|
// Add any expo mocks here if needed
|
|
}));
|
|
|
|
// Mock Expo modules
|
|
jest.mock('expo-router', () => ({
|
|
router: {
|
|
push: jest.fn(),
|
|
replace: jest.fn(),
|
|
back: jest.fn(),
|
|
setParams: jest.fn(),
|
|
},
|
|
useLocalSearchParams: jest.fn(() => ({})),
|
|
useRouter: jest.fn(() => ({
|
|
push: jest.fn(),
|
|
replace: jest.fn(),
|
|
back: jest.fn(),
|
|
})),
|
|
useSegments: jest.fn(() => []),
|
|
usePathname: jest.fn(() => '/'),
|
|
}));
|
|
|
|
jest.mock('expo-secure-store', () => ({
|
|
getItemAsync: jest.fn(),
|
|
setItemAsync: jest.fn(),
|
|
deleteItemAsync: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('expo-crypto', () => {
|
|
return {
|
|
getRandomBytesAsync: jest.fn((size) => {
|
|
const bytes = new Uint8Array(size);
|
|
for (let i = 0; i < size; i++) {
|
|
bytes[i] = (i * 7 + 13) % 256;
|
|
}
|
|
return Promise.resolve(bytes);
|
|
}),
|
|
digestStringAsync: jest.fn((algorithm, data) => {
|
|
const hash = Buffer.from(data).toString('base64').substring(0, 64).padEnd(64, '0');
|
|
return Promise.resolve(hash);
|
|
}),
|
|
CryptoDigestAlgorithm: {
|
|
SHA256: 'SHA256',
|
|
},
|
|
};
|
|
}, { virtual: true });
|
|
|
|
jest.mock('expo-image-picker', () => ({
|
|
requestMediaLibraryPermissionsAsync: jest.fn(() =>
|
|
Promise.resolve({ status: 'granted' })
|
|
),
|
|
launchImageLibraryAsync: jest.fn(() =>
|
|
Promise.resolve({
|
|
canceled: false,
|
|
assets: [{ uri: 'file://test-image.jpg' }],
|
|
})
|
|
),
|
|
}));
|
|
|
|
// Mock AsyncStorage
|
|
jest.mock('@react-native-async-storage/async-storage', () => ({
|
|
getItem: jest.fn(),
|
|
setItem: jest.fn(),
|
|
removeItem: jest.fn(),
|
|
clear: jest.fn(),
|
|
getAllKeys: jest.fn(() => Promise.resolve([])),
|
|
multiGet: jest.fn(() => Promise.resolve([])),
|
|
multiSet: jest.fn(() => Promise.resolve()),
|
|
multiRemove: jest.fn(() => Promise.resolve()),
|
|
}));
|
|
|
|
// Mock native modules
|
|
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper', () => ({
|
|
default: {},
|
|
}), { virtual: true });
|
|
|
|
// Silence console warnings in tests
|
|
global.console = {
|
|
...console,
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
};
|