- Create wifiPasswordStore service for encrypted password storage - Replace AsyncStorage with SecureStore for WiFi credentials - Add automatic migration from AsyncStorage to SecureStore - Integrate WiFi password cleanup into logout process - Add comprehensive test suite for password storage operations - Update setup-wifi screen to use secure storage Security improvements: - WiFi passwords now stored encrypted via expo-secure-store - Passwords automatically cleared on user logout - Seamless migration for existing users 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
/**
|
|
* WiFi Password Secure Storage Service
|
|
*
|
|
* Provides secure storage for WiFi passwords using expo-secure-store.
|
|
* Replaces AsyncStorage for improved security.
|
|
*/
|
|
|
|
import * as SecureStore from 'expo-secure-store';
|
|
|
|
const WIFI_PASSWORDS_KEY = 'WIFI_PASSWORDS';
|
|
const LEGACY_SINGLE_PASSWORD_KEY = 'LAST_WIFI_PASSWORD';
|
|
|
|
export interface WiFiPasswordMap {
|
|
[ssid: string]: string;
|
|
}
|
|
|
|
/**
|
|
* Save WiFi password for a specific network
|
|
* @param ssid Network SSID
|
|
* @param password Network password
|
|
*/
|
|
export async function saveWiFiPassword(ssid: string, password: string): Promise<void> {
|
|
try {
|
|
// Get existing passwords
|
|
const existing = await getAllWiFiPasswords();
|
|
|
|
// Add/update the password
|
|
existing[ssid] = password;
|
|
|
|
// Save back to SecureStore
|
|
await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, JSON.stringify(existing));
|
|
|
|
console.log('[WiFiPasswordStore] Password saved for network:', ssid);
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Failed to save password:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get WiFi password for a specific network
|
|
* @param ssid Network SSID
|
|
* @returns Password or undefined if not found
|
|
*/
|
|
export async function getWiFiPassword(ssid: string): Promise<string | undefined> {
|
|
try {
|
|
const passwords = await getAllWiFiPasswords();
|
|
return passwords[ssid];
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Failed to get password:', error);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all saved WiFi passwords
|
|
* @returns Map of SSID to password
|
|
*/
|
|
export async function getAllWiFiPasswords(): Promise<WiFiPasswordMap> {
|
|
try {
|
|
const stored = await SecureStore.getItemAsync(WIFI_PASSWORDS_KEY);
|
|
|
|
if (stored) {
|
|
return JSON.parse(stored);
|
|
}
|
|
|
|
return {};
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Failed to get all passwords:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove WiFi password for a specific network
|
|
* @param ssid Network SSID
|
|
*/
|
|
export async function removeWiFiPassword(ssid: string): Promise<void> {
|
|
try {
|
|
const existing = await getAllWiFiPasswords();
|
|
|
|
// Remove the password
|
|
delete existing[ssid];
|
|
|
|
// Save back to SecureStore
|
|
if (Object.keys(existing).length > 0) {
|
|
await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, JSON.stringify(existing));
|
|
} else {
|
|
// If no passwords left, remove the key entirely
|
|
await SecureStore.deleteItemAsync(WIFI_PASSWORDS_KEY);
|
|
}
|
|
|
|
console.log('[WiFiPasswordStore] Password removed for network:', ssid);
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Failed to remove password:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all saved WiFi passwords
|
|
* Should be called on logout
|
|
*/
|
|
export async function clearAllWiFiPasswords(): Promise<void> {
|
|
try {
|
|
await SecureStore.deleteItemAsync(WIFI_PASSWORDS_KEY);
|
|
console.log('[WiFiPasswordStore] All WiFi passwords cleared');
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Failed to clear passwords:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrate WiFi passwords from AsyncStorage to SecureStore
|
|
* This function should be called once during app startup to migrate existing data
|
|
*/
|
|
export async function migrateFromAsyncStorage(): Promise<void> {
|
|
try {
|
|
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
|
|
// Check if migration already done
|
|
const existing = await SecureStore.getItemAsync(WIFI_PASSWORDS_KEY);
|
|
if (existing) {
|
|
console.log('[WiFiPasswordStore] Migration already completed');
|
|
return;
|
|
}
|
|
|
|
// Try to get old data from AsyncStorage
|
|
const oldPasswords = await AsyncStorage.getItem('WIFI_PASSWORDS');
|
|
|
|
if (oldPasswords) {
|
|
// Migrate to SecureStore
|
|
await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, oldPasswords);
|
|
|
|
// Remove from AsyncStorage
|
|
await AsyncStorage.removeItem('WIFI_PASSWORDS');
|
|
await AsyncStorage.removeItem(LEGACY_SINGLE_PASSWORD_KEY);
|
|
|
|
console.log('[WiFiPasswordStore] Successfully migrated passwords from AsyncStorage');
|
|
} else {
|
|
console.log('[WiFiPasswordStore] No passwords to migrate');
|
|
}
|
|
} catch (error) {
|
|
console.error('[WiFiPasswordStore] Migration failed:', error);
|
|
// Don't throw - migration failure shouldn't break the app
|
|
}
|
|
}
|