/** * WiFi Password Secure Storage Service * * Provides secure storage for WiFi passwords using expo-secure-store with encryption. * All passwords are encrypted using AES-256-GCM before storage. */ import * as SecureStore from 'expo-secure-store'; import { encrypt, decrypt, isEncrypted } from './encryption'; 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 { try { // Get existing passwords const existing = await getAllWiFiPasswords(); // Encrypt the password const encryptedPassword = await encrypt(password); // Add/update the password existing[ssid] = encryptedPassword; // Save back to SecureStore await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, JSON.stringify(existing)); console.log('[WiFiPasswordStore] Encrypted 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 Decrypted password or undefined if not found */ export async function getWiFiPassword(ssid: string): Promise { try { const passwords = await getAllWiFiPasswords(); const encryptedPassword = passwords[ssid]; if (!encryptedPassword) { return undefined; } // Decrypt the password const decryptedPassword = await decrypt(encryptedPassword); return decryptedPassword; } catch (error) { console.error('[WiFiPasswordStore] Failed to get password:', error); return undefined; } } /** * Get all saved WiFi passwords (encrypted format) * Internal helper - passwords remain encrypted * @returns Map of SSID to encrypted password */ async function getAllWiFiPasswordsEncrypted(): Promise { 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 {}; } } /** * Get all saved WiFi passwords (decrypted) * @returns Map of SSID to decrypted password */ export async function getAllWiFiPasswords(): Promise { try { const encryptedPasswords = await getAllWiFiPasswordsEncrypted(); const decryptedPasswords: WiFiPasswordMap = {}; // Decrypt each password for (const [ssid, encryptedPassword] of Object.entries(encryptedPasswords)) { try { decryptedPasswords[ssid] = await decrypt(encryptedPassword); } catch (error) { console.error(`[WiFiPasswordStore] Failed to decrypt password for ${ssid}:`, error); // Skip this password if decryption fails } } return decryptedPasswords; } 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 { try { const existing = await getAllWiFiPasswordsEncrypted(); // 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 { 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 unencrypted passwords to encrypted format * Checks each stored password and encrypts if needed */ export async function migrateToEncrypted(): Promise { try { const stored = await SecureStore.getItemAsync(WIFI_PASSWORDS_KEY); if (!stored) { console.log('[WiFiPasswordStore] No passwords to migrate'); return; } const passwords: WiFiPasswordMap = JSON.parse(stored); let migrated = 0; const encryptedPasswords: WiFiPasswordMap = {}; // Check each password for (const [ssid, password] of Object.entries(passwords)) { if (isEncrypted(password)) { // Already encrypted encryptedPasswords[ssid] = password; } else { // Encrypt the plaintext password encryptedPasswords[ssid] = await encrypt(password); migrated++; } } // Save back if any were migrated if (migrated > 0) { await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, JSON.stringify(encryptedPasswords)); console.log(`[WiFiPasswordStore] Successfully migrated ${migrated} passwords to encrypted format`); } else { console.log('[WiFiPasswordStore] All passwords already encrypted'); } } catch (error) { console.error('[WiFiPasswordStore] Migration to encrypted format failed:', error); // Don't throw - migration failure shouldn't break the app } } /** * Migrate WiFi passwords from AsyncStorage to SecureStore with encryption * This function should be called once during app startup to migrate existing data */ export async function migrateFromAsyncStorage(): Promise { try { // eslint-disable-next-line @typescript-eslint/no-require-imports 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 from AsyncStorage already completed'); // Still run encryption migration in case they were migrated but not encrypted await migrateToEncrypted(); return; } // Try to get old data from AsyncStorage const oldPasswords = await AsyncStorage.getItem('WIFI_PASSWORDS'); if (oldPasswords) { const passwords: WiFiPasswordMap = JSON.parse(oldPasswords); const encryptedPasswords: WiFiPasswordMap = {}; // Encrypt each password during migration for (const [ssid, password] of Object.entries(passwords)) { encryptedPasswords[ssid] = await encrypt(password); } // Migrate to SecureStore with encryption await SecureStore.setItemAsync(WIFI_PASSWORDS_KEY, JSON.stringify(encryptedPasswords)); // Remove from AsyncStorage await AsyncStorage.removeItem('WIFI_PASSWORDS'); await AsyncStorage.removeItem(LEGACY_SINGLE_PASSWORD_KEY); console.log('[WiFiPasswordStore] Successfully migrated and encrypted passwords from AsyncStorage'); } else { console.log('[WiFiPasswordStore] No passwords to migrate from AsyncStorage'); } } catch (error) { console.error('[WiFiPasswordStore] Migration from AsyncStorage failed:', error); // Don't throw - migration failure shouldn't break the app } }