Skip to content

Commit 823bbd1

Browse files
Merge pull request #75 from alrescha79-cmd/feat/wifi-password-encryption
feat(wifi): implement WiFi password encryption with RSA-OAEP SHA-1
2 parents 71f66f1 + e21149b commit 823bbd1

5 files changed

Lines changed: 359 additions & 82 deletions

File tree

src/app/(tabs)/wifi.tsx

Lines changed: 119 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from 'react-native';
1919
import { MaterialIcons } from '@expo/vector-icons';
2020
import { useTheme } from '@/theme';
21-
import { Card, CardHeader, InfoRow, Button, ThemedAlertHelper, DeviceDetailModal, SelectionModal, MeshGradientBackground, AnimatedScreen, ThemedSwitch, BouncingDots, ModernRefreshIndicator } from '@/components';
21+
import { Card, CardHeader, InfoRow, Button, ThemedAlertHelper, DeviceDetailModal, SelectionModal, MeshGradientBackground, AnimatedScreen, ThemedSwitch, BouncingDots, ModernRefreshIndicator, KeyboardAnimatedView } from '@/components';
2222
import { ConnectedDevice } from '@/types';
2323
import { useAuthStore } from '@/stores/auth.store';
2424
import { useWiFiStore } from '@/stores/wifi.store';
@@ -386,18 +386,67 @@ export default function WiFiScreen() {
386386
return;
387387
}
388388

389+
// Check if password is being changed - warn user about disconnect
390+
const isPasswordChange = formPassword.length >= 8 && formPassword !== (wifiSettings?.password || '');
391+
392+
if (isPasswordChange) {
393+
// Show confirmation dialog for password change
394+
ThemedAlertHelper.alert(
395+
t('wifi.passwordChangeWarning'),
396+
t('wifi.passwordChangeMessage'),
397+
[
398+
{ text: t('common.cancel'), style: 'cancel' },
399+
{
400+
text: t('wifi.changePassword'),
401+
style: 'destructive',
402+
onPress: () => doSaveSettings(true),
403+
},
404+
]
405+
);
406+
} else {
407+
// No password change, just save SSID or other settings
408+
doSaveSettings(false);
409+
}
410+
};
411+
412+
const doSaveSettings = async (isPasswordChange: boolean) => {
389413
setIsSaving(true);
390414
try {
391-
await wifiService.setWiFiSettings({
415+
await wifiService!.setWiFiSettings({
392416
ssid: formSsid,
393417
password: formPassword,
394418
securityMode: formSecurityMode,
395419
});
396-
ThemedAlertHelper.alert(t('common.success'), t('wifi.settingsSaved'));
397-
handleRefresh();
398-
} catch (error) {
420+
421+
if (isPasswordChange) {
422+
// Password changed - WiFi will disconnect
423+
// Show success message and don't try to refresh (will fail due to disconnect)
424+
ThemedAlertHelper.alert(
425+
t('common.success'),
426+
t('wifi.passwordChangeSuccess')
427+
);
428+
} else {
429+
ThemedAlertHelper.alert(t('common.success'), t('wifi.settingsSaved'));
430+
handleRefresh();
431+
}
432+
} catch (error: any) {
399433
console.error('[WiFi UI] setWiFiSettings error:', error);
400-
ThemedAlertHelper.alert(t('common.error'), t('alerts.failedSaveWifi'));
434+
435+
// Check if error is network-related (expected after password change)
436+
const errorMessage = error?.message || '';
437+
if (isPasswordChange && (
438+
errorMessage.includes('Network') ||
439+
errorMessage.includes('timeout') ||
440+
errorMessage.includes('Failed to fetch')
441+
)) {
442+
// This is expected - WiFi disconnected due to password change
443+
ThemedAlertHelper.alert(
444+
t('wifi.passwordChanged'),
445+
t('wifi.reconnectWithNewPassword')
446+
);
447+
} else {
448+
ThemedAlertHelper.alert(t('common.error'), t('alerts.failedSaveWifi'));
449+
}
401450
} finally {
402451
setIsSaving(false);
403452
}
@@ -636,6 +685,8 @@ export default function WiFiScreen() {
636685
progressViewOffset={50}
637686
/>
638687
}
688+
keyboardShouldPersistTaps="handled"
689+
keyboardDismissMode="interactive"
639690
>
640691
<View style={styles.header} />
641692

@@ -889,12 +940,13 @@ export default function WiFiScreen() {
889940
/>
890941
</View>
891942

892-
{/* Security Mode Dropdown - DISABLED (encryption not supported yet) */}
893-
<View style={[styles.formGroup, { opacity: 0.5 }]}>
943+
{/* Security Mode Dropdown */}
944+
<View style={styles.formGroup}>
894945
<Text style={[typography.subheadline, { color: colors.textSecondary, marginBottom: 6 }]}>
895946
{t('wifi.securityMode')}
896947
</Text>
897-
<View
948+
<TouchableOpacity
949+
onPress={() => setShowSecurityDropdown(true)}
898950
style={[styles.dropdown, {
899951
backgroundColor: colors.card,
900952
borderColor: colors.border
@@ -903,18 +955,13 @@ export default function WiFiScreen() {
903955
<Text style={[typography.body, { color: colors.text }]}>
904956
{getSecurityModeLabel(formSecurityMode)}
905957
</Text>
906-
<MaterialIcons name="lock" size={20} color={colors.textSecondary} />
907-
</View>
908-
<TouchableOpacity onPress={() => Linking.openURL(`http://${credentials?.modemIp || '192.168.8.1'}`)}>
909-
<Text style={[typography.caption1, { color: colors.primary, marginTop: 4, fontStyle: 'italic' }]}>
910-
{t('wifi.useWebInterface')}
911-
</Text>
958+
<MaterialIcons name="arrow-drop-down" size={24} color={colors.textSecondary} />
912959
</TouchableOpacity>
913960
</View>
914961

915-
{/* Password Input - DISABLED (encryption not supported yet) */}
962+
{/* Password Input */}
916963
{formSecurityMode !== 'OPEN' && (
917-
<View style={[styles.formGroup, { opacity: 0.5 }]}>
964+
<View style={styles.formGroup}>
918965
<Text style={[typography.subheadline, { color: colors.textSecondary, marginBottom: 6 }]}>
919966
{t('wifi.password')}
920967
</Text>
@@ -927,12 +974,15 @@ export default function WiFiScreen() {
927974
paddingRight: 48
928975
}]}
929976
value={formPassword}
930-
editable={false}
931-
placeholder="********"
977+
onChangeText={setFormPassword}
978+
placeholder={t('wifi.enterPassword')}
932979
placeholderTextColor={colors.textSecondary}
933-
secureTextEntry={true}
980+
secureTextEntry={!showPassword}
981+
autoCapitalize="none"
982+
autoCorrect={false}
934983
/>
935-
<View
984+
<TouchableOpacity
985+
onPress={() => setShowPassword(!showPassword)}
936986
style={{
937987
position: 'absolute',
938988
right: 12,
@@ -943,40 +993,61 @@ export default function WiFiScreen() {
943993
}}
944994
>
945995
<MaterialIcons
946-
name="lock"
996+
name={showPassword ? 'visibility' : 'visibility-off'}
947997
size={22}
948998
color={colors.textSecondary}
949999
/>
950-
</View>
1000+
</TouchableOpacity>
9511001
</View>
952-
<TouchableOpacity onPress={() => Linking.openURL(`http://${credentials?.modemIp || '192.168.8.1'}`)}>
953-
<Text style={[typography.caption1, { color: colors.primary, marginTop: 4, fontStyle: 'italic' }]}>
954-
{t('wifi.useWebInterface')}
955-
</Text>
956-
</TouchableOpacity>
1002+
<Text style={[typography.caption2, { color: colors.textSecondary, marginTop: 4 }]}>
1003+
{t('wifi.passwordHint')}
1004+
</Text>
9571005
</View>
9581006
)}
9591007

960-
{/* Save Button */}
961-
<TouchableOpacity
962-
style={[
963-
styles.saveButton,
964-
{
965-
backgroundColor: hasChanges ? colors.primary : colors.border,
966-
opacity: hasChanges && !isSaving ? 1 : 0.6
967-
}
1008+
{/* Security Mode Selection Modal */}
1009+
<SelectionModal
1010+
visible={showSecurityDropdown}
1011+
title={t('wifi.securityMode')}
1012+
options={[
1013+
{ label: t('wifi.authModes.wpa2Psk'), value: 'WPA2-PSK' },
1014+
{ label: t('wifi.authModes.wpaPsk'), value: 'WPA-PSK' },
1015+
{ label: t('wifi.authModes.wpaWpa2Psk'), value: 'WPA/WPA2-PSK' },
1016+
{ label: t('wifi.authModes.wpa2Enterprise'), value: 'WPA2' },
1017+
{ label: t('wifi.authModes.wpaEnterprise'), value: 'WPA' },
1018+
{ label: t('wifi.authModes.shared'), value: 'SHARED' },
1019+
{ label: t('wifi.authModes.open'), value: 'OPEN' },
9681020
]}
969-
onPress={handleSaveSettings}
970-
disabled={!hasChanges || isSaving}
971-
>
972-
{isSaving ? (
973-
<BouncingDots size="small" color="#FFFFFF" />
974-
) : (
975-
<Text style={[typography.body, { color: '#FFFFFF', fontWeight: '600' }]}>
976-
{t('wifi.saveChanges')}
977-
</Text>
978-
)}
979-
</TouchableOpacity>
1021+
selectedValue={formSecurityMode}
1022+
onSelect={(val) => {
1023+
setFormSecurityMode(val);
1024+
setShowSecurityDropdown(false);
1025+
}}
1026+
onClose={() => setShowSecurityDropdown(false)}
1027+
/>
1028+
1029+
{/* Save Button - only visible when there are changes */}
1030+
{hasChanges && (
1031+
<TouchableOpacity
1032+
style={[
1033+
styles.saveButton,
1034+
{
1035+
backgroundColor: colors.primary,
1036+
opacity: isSaving ? 0.6 : 1
1037+
}
1038+
]}
1039+
onPress={handleSaveSettings}
1040+
disabled={isSaving}
1041+
>
1042+
{isSaving ? (
1043+
<BouncingDots size="small" color="#FFFFFF" />
1044+
) : (
1045+
<Text style={[typography.body, { color: '#FFFFFF', fontWeight: '600' }]}>
1046+
{t('wifi.saveChanges')}
1047+
</Text>
1048+
)}
1049+
</TouchableOpacity>
1050+
)}
9801051
</View>
9811052
)}
9821053
</Card>
@@ -1434,7 +1505,7 @@ export default function WiFiScreen() {
14341505
/>
14351506
</ScrollView>
14361507
</MeshGradientBackground>
1437-
</AnimatedScreen>
1508+
</AnimatedScreen >
14381509
);
14391510
}
14401511

src/i18n/en.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,22 @@
234234
"securityMode": "Security Mode",
235235
"saveChanges": "Save Changes",
236236
"settingsSaved": "WiFi settings saved successfully",
237+
"passwordChangeWarning": "Change WiFi Password",
238+
"passwordChangeMessage": "Changing the WiFi password will disconnect all devices including this one. You will need to reconnect with the new password.",
239+
"changePassword": "Change Password",
240+
"passwordChangeSuccess": "WiFi password changed! Please reconnect to WiFi with your new password.",
241+
"passwordChanged": "Password Changed",
242+
"reconnectWithNewPassword": "WiFi password was changed successfully. Please reconnect to WiFi using your new password.",
243+
"open": "No Security (Open)",
244+
"authModes": {
245+
"open": "Open (No Security)",
246+
"shared": "Shared Key",
247+
"wpaPsk": "WPA-PSK (TKIP)",
248+
"wpa2Psk": "WPA2-PSK (AES)",
249+
"wpaWpa2Psk": "WPA/WPA2-PSK (Mixed)",
250+
"wpaEnterprise": "WPA Enterprise",
251+
"wpa2Enterprise": "WPA2 Enterprise"
252+
},
237253
"enabled": "Enabled",
238254
"disabled": "Disabled",
239255
"guestWifi": "Guest WiFi",
@@ -269,6 +285,7 @@
269285
"guestSsid": "WiFi Name",
270286
"enterSsid": "Enter WiFi name",
271287
"enterPassword": "Enter password",
288+
"passwordHint": "8-63 characters",
272289
"security": "Security",
273290
"securityEncrypted": "Encrypted",
274291
"guestSettingsSaved": "Guest WiFi settings saved successfully",

src/i18n/id.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,22 @@
234234
"securityMode": "Mode Keamanan",
235235
"saveChanges": "Simpan Perubahan",
236236
"settingsSaved": "Pengaturan WiFi berhasil disimpan",
237+
"passwordChangeWarning": "Ubah Kata Sandi WiFi",
238+
"passwordChangeMessage": "Mengubah kata sandi WiFi akan memutuskan semua perangkat termasuk perangkat ini. Anda perlu menyambung ulang dengan kata sandi baru.",
239+
"changePassword": "Ubah Kata Sandi",
240+
"passwordChangeSuccess": "Kata sandi WiFi berhasil diubah! Silakan sambung ulang ke WiFi dengan kata sandi baru.",
241+
"passwordChanged": "Kata Sandi Diubah",
242+
"reconnectWithNewPassword": "Kata sandi WiFi berhasil diubah. Silakan sambung ulang ke WiFi menggunakan kata sandi baru.",
243+
"open": "Tanpa Keamanan (Terbuka)",
244+
"authModes": {
245+
"open": "Terbuka (Tanpa Keamanan)",
246+
"shared": "Kunci Bersama",
247+
"wpaPsk": "WPA-PSK (TKIP)",
248+
"wpa2Psk": "WPA2-PSK (AES)",
249+
"wpaWpa2Psk": "WPA/WPA2-PSK (Campuran)",
250+
"wpaEnterprise": "WPA Enterprise",
251+
"wpa2Enterprise": "WPA2 Enterprise"
252+
},
237253
"enabled": "Aktif",
238254
"disabled": "Nonaktif",
239255
"guestWifi": "WiFi Tamu",
@@ -269,6 +285,7 @@
269285
"guestSsid": "Nama WiFi",
270286
"enterSsid": "Masukkan nama WiFi",
271287
"enterPassword": "Masukkan kata sandi",
288+
"passwordHint": "8-63 karakter",
272289
"security": "Keamanan",
273290
"securityEncrypted": "Terenkripsi",
274291
"guestSettingsSaved": "Pengaturan WiFi Tamu berhasil disimpan",

0 commit comments

Comments
 (0)