diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 58f85feb0..7cc76d6c6 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1831,6 +1831,7 @@ void changeBot({Function()? callback}) async { void change2fa({Function()? callback}) async { if (bind.mainHasValid2FaSync()) { await bind.mainSetOption(key: "2fa", value: ""); + await bind.mainClearTrustedDevices(); callback?.call(); return; } @@ -1898,6 +1899,7 @@ void enter2FaDialog( SessionID sessionId, OverlayDialogManager dialogManager) async { final controller = TextEditingController(); final RxBool submitReady = false.obs; + final RxBool trustThisDevice = false.obs; dialogManager.dismissAll(); dialogManager.show((setState, close, context) { @@ -1907,7 +1909,7 @@ void enter2FaDialog( } submit() { - gFFI.send2FA(sessionId, controller.text.trim()); + gFFI.send2FA(sessionId, controller.text.trim(), trustThisDevice.value); close(); dialogManager.showLoading(translate('Logging in...'), onCancel: closeConnection); @@ -1921,9 +1923,27 @@ void enter2FaDialog( onChanged: () => submitReady.value = codeField.isReady, ); + final trustField = Obx(() => CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text(translate("Trust this device")), + value: trustThisDevice.value, + onChanged: (value) { + if (value == null) return; + trustThisDevice.value = value; + }, + )); + return CustomAlertDialog( title: Text(translate('enter-2fa-title')), - content: codeField, + content: Column( + children: [ + codeField, + if (bind.sessionGetEnableTrustedDevices(sessionId: sessionId)) + trustField, + ], + ), actions: [ dialogButton('Cancel', onPressed: cancel, @@ -2313,3 +2333,157 @@ void checkUnlockPinDialog(String correctPin, Function() passCallback) { ); }); } + +void confrimDeleteTrustedDevicesDialog( + RxList trustedDevices, RxList selectedDevices) { + CommonConfirmDialog(gFFI.dialogManager, '${translate('Confirm Delete')}?', + () async { + if (selectedDevices.isEmpty) return; + if (selectedDevices.length == trustedDevices.length) { + await bind.mainClearTrustedDevices(); + trustedDevices.clear(); + selectedDevices.clear(); + } else { + final json = jsonEncode(selectedDevices.map((e) => e.toList()).toList()); + await bind.mainRemoveTrustedDevices(json: json); + trustedDevices.removeWhere((element) { + return selectedDevices.contains(element.hwid); + }); + selectedDevices.clear(); + } + }); +} + +void manageTrustedDeviceDialog() async { + RxList trustedDevices = (await TrustedDevice.get()).obs; + RxList selectedDevices = RxList.empty(); + gFFI.dialogManager.show((setState, close, context) { + return CustomAlertDialog( + title: Text(translate("Manage trusted devices")), + content: trustedDevicesTable(trustedDevices, selectedDevices), + actions: [ + Obx(() => dialogButton(translate("Delete"), + onPressed: selectedDevices.isEmpty + ? null + : () { + confrimDeleteTrustedDevicesDialog( + trustedDevices, + selectedDevices, + ); + }, + isOutline: false) + .marginOnly(top: 12)), + dialogButton(translate("Close"), onPressed: close, isOutline: true) + .marginOnly(top: 12), + ], + onCancel: close, + ); + }); +} + +class TrustedDevice { + late final Uint8List hwid; + late final int time; + late final String id; + late final String name; + late final String platform; + + TrustedDevice.fromJson(Map json) { + final hwidList = json['hwid'] as List; + hwid = Uint8List.fromList(hwidList.cast()); + time = json['time']; + id = json['id']; + name = json['name']; + platform = json['platform']; + } + + String daysRemaining() { + final expiry = time + 90 * 24 * 60 * 60 * 1000; + final remaining = expiry - DateTime.now().millisecondsSinceEpoch; + if (remaining < 0) { + return '0'; + } + return (remaining / (24 * 60 * 60 * 1000)).toStringAsFixed(0); + } + + static Future> get() async { + final List devices = List.empty(growable: true); + try { + final devicesJson = await bind.mainGetTrustedDevices(); + if (devicesJson.isNotEmpty) { + final devicesList = json.decode(devicesJson); + if (devicesList is List) { + for (var device in devicesList) { + devices.add(TrustedDevice.fromJson(device)); + } + } + } + } catch (e) { + print(e.toString()); + } + devices.sort((a, b) => b.time.compareTo(a.time)); + return devices; + } +} + +Widget trustedDevicesTable( + RxList devices, RxList selectedDevices) { + RxBool selectAll = false.obs; + setSelectAll() { + if (selectedDevices.isNotEmpty && + selectedDevices.length == devices.length) { + selectAll.value = true; + } else { + selectAll.value = false; + } + } + + devices.listen((_) { + setSelectAll(); + }); + selectedDevices.listen((_) { + setSelectAll(); + }); + return FittedBox( + child: Obx(() => DataTable( + columns: [ + DataColumn( + label: Checkbox( + value: selectAll.value, + onChanged: (value) { + if (value == true) { + selectedDevices.clear(); + selectedDevices.addAll(devices.map((e) => e.hwid)); + } else { + selectedDevices.clear(); + } + }, + )), + DataColumn(label: Text(translate('Platform'))), + DataColumn(label: Text(translate('ID'))), + DataColumn(label: Text(translate('Username'))), + DataColumn(label: Text(translate('Days remaining'))), + ], + rows: devices.map((device) { + return DataRow(cells: [ + DataCell(Checkbox( + value: selectedDevices.contains(device.hwid), + onChanged: (value) { + if (value == null) return; + if (value) { + selectedDevices.remove(device.hwid); + selectedDevices.add(device.hwid); + } else { + selectedDevices.remove(device.hwid); + } + }, + )), + DataCell(Text(device.platform)), + DataCell(Text(device.id)), + DataCell(Text(device.name)), + DataCell(Text(device.daysRemaining())), + ]); + }).toList(), + )), + ); +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 5af3f6d25..b836200a4 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -136,6 +136,7 @@ const String kOptionAllowRemoveWallpaper = "allow-remove-wallpaper"; const String kOptionStopService = "stop-service"; const String kOptionDirectxCapture = "enable-directx-capture"; const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification"; +const String kOptionEnableTrustedDevices = "enable-trusted-devices"; // buildin opitons const String kOptionHideServerSetting = "hide-server-settings"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index a016a61ae..6d9f92b59 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -783,8 +783,33 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { onChangedBot(!hasBot.value); }, ).marginOnly(left: _kCheckBoxLeftMargin + 30); + + final trust = Row( + children: [ + Flexible( + child: Tooltip( + waitDuration: Duration(milliseconds: 300), + message: translate("enable-trusted-devices-tip"), + child: _OptionCheckBox(context, "Enable trusted devices", + kOptionEnableTrustedDevices, + enabled: !locked, update: (v) { + setState(() {}); + }), + ), + ), + if (mainGetBoolOptionSync(kOptionEnableTrustedDevices)) + ElevatedButton( + onPressed: locked + ? null + : () { + manageTrustedDeviceDialog(); + }, + child: Text(translate('Manage trusted devices'))) + ], + ).marginOnly(left: 30); + return Column( - children: [tfa, bot], + children: [tfa, bot, trust], ); } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index c817fda4e..98a2b9f50 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; @@ -87,6 +88,7 @@ class _SettingsState extends State with WidgetsBindingObserver { var _hideServer = false; var _hideProxy = false; var _hideNetwork = false; + var _enableTrustedDevices = false; _SettingsState() { _enableAbr = option2bool( @@ -113,6 +115,7 @@ class _SettingsState extends State with WidgetsBindingObserver { _hideProxy = bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; _hideNetwork = bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) == 'Y'; + _enableTrustedDevices = mainGetBoolOptionSync(kOptionEnableTrustedDevices); } @override @@ -243,18 +246,57 @@ class _SettingsState extends State with WidgetsBindingObserver { ], )); final List enhancementsTiles = []; - final List shareScreenTiles = [ + final enable2fa = bind.mainHasValid2FaSync(); + final List tfaTiles = [ SettingsTile.switchTile( title: Text(translate('enable-2fa-title')), - initialValue: bind.mainHasValid2FaSync(), - onToggle: (_) async { + initialValue: enable2fa, + onToggle: (v) async { update() async { setState(() {}); } - change2fa(callback: update); + if (v == false) { + CommonConfirmDialog( + gFFI.dialogManager, translate('cancel-2fa-confirm-tip'), () { + change2fa(callback: update); + }); + } else { + change2fa(callback: update); + } }, ), + if (enable2fa) + SettingsTile.switchTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translate('Enable trusted devices')), + Text(translate('enable-trusted-devices-tip'), + style: Theme.of(context).textTheme.bodySmall), + ], + ), + initialValue: _enableTrustedDevices, + onToggle: isOptionFixed(kOptionEnableTrustedDevices) + ? null + : (v) async { + mainSetBoolOption(kOptionEnableTrustedDevices, v); + setState(() { + _enableTrustedDevices = v; + }); + }, + ), + if (enable2fa && _enableTrustedDevices) + SettingsTile( + title: Text(translate('Manage trusted devices')), + trailing: Icon(Icons.arrow_forward_ios), + onPressed: (context) { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return _ManageTrustedDevices(); + })); + }) + ]; + final List shareScreenTiles = [ SettingsTile.switchTile( title: Text(translate('Deny LAN discovery')), initialValue: _denyLANDiscovery, @@ -642,6 +684,11 @@ class _SettingsState extends State with WidgetsBindingObserver { ), ], ), + if (isAndroid && + !disabledSettings && + !outgoingOnly && + !hideSecuritySettings) + SettingsSection(title: Text('2FA'), tiles: tfaTiles), if (isAndroid && !disabledSettings && !outgoingOnly && @@ -963,6 +1010,51 @@ class __DisplayPageState extends State<_DisplayPage> { } } +class _ManageTrustedDevices extends StatefulWidget { + const _ManageTrustedDevices(); + + @override + State<_ManageTrustedDevices> createState() => __ManageTrustedDevicesState(); +} + +class __ManageTrustedDevicesState extends State<_ManageTrustedDevices> { + RxList trustedDevices = RxList.empty(growable: true); + RxList selectedDevices = RxList.empty(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(translate('Manage trusted devices')), + centerTitle: true, + actions: [ + Obx(() => IconButton( + icon: Icon(Icons.delete, color: Colors.white), + onPressed: selectedDevices.isEmpty + ? null + : () { + confrimDeleteTrustedDevicesDialog( + trustedDevices, selectedDevices); + })) + ], + ), + body: FutureBuilder( + future: TrustedDevice.get(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + final devices = snapshot.data as List; + trustedDevices = devices.obs; + return trustedDevicesTable(trustedDevices, selectedDevices); + }), + ); + } +} + class _RadioEntry { final String label; final String value; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 6a9009026..050a92a5f 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -2611,8 +2611,9 @@ class FFI { remember: remember); } - void send2FA(SessionID sessionId, String code) { - bind.sessionSend2Fa(sessionId: sessionId, code: code); + void send2FA(SessionID sessionId, String code, bool trustThisDevice) { + bind.sessionSend2Fa( + sessionId: sessionId, code: code, trustThisDevice: trustThisDevice); } /// Close the remote session. diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 7795cb824..3f3846e08 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -142,7 +142,10 @@ class RustdeskImpl { } Future sessionSend2Fa( - {required UuidValue sessionId, required String code, dynamic hint}) { + {required UuidValue sessionId, + required String code, + required bool trustThisDevice, + dynamic hint}) { return Future(() => js.context.callMethod('setByName', ['send_2fa', code])); } @@ -1630,5 +1633,22 @@ class RustdeskImpl { throw UnimplementedError(); } + bool sessionGetEnableTrustedDevices( + {required UuidValue sessionId, dynamic hint}) { + throw UnimplementedError(); + } + + Future mainGetTrustedDevices({dynamic hint}) { + throw UnimplementedError(); + } + + Future mainRemoveTrustedDevices({required String json, dynamic hint}) { + throw UnimplementedError(); + } + + Future mainClearTrustedDevices({dynamic hint}) { + throw UnimplementedError(); + } + void dispose() {} } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index d7a8cf0a7..497bdee9b 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -82,10 +82,12 @@ message LoginRequest { string version = 11; OSLogin os_login = 12; string my_platform = 13; + bytes hwid = 14; } message Auth2FA { string code = 1; + bytes hwid = 2; } message ChatMessage { string text = 1; } @@ -137,6 +139,7 @@ message LoginResponse { string error = 1; PeerInfo peer_info = 2; } + bool enable_trusted_devices = 3; } message TouchScaleUpdate { diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 2dfa88b96..d0b908b55 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -10,6 +10,7 @@ use std::{ }; use anyhow::Result; +use bytes::Bytes; use rand::Rng; use regex::Regex; use serde as de; @@ -52,6 +53,7 @@ lazy_static::lazy_static! { static ref CONFIG: RwLock = RwLock::new(Config::load()); static ref CONFIG2: RwLock = RwLock::new(Config2::load()); static ref LOCAL_CONFIG: RwLock = RwLock::new(LocalConfig::load()); + static ref TRUSTED_DEVICES: RwLock<(Vec, bool)> = Default::default(); static ref ONLINE: Mutex> = Default::default(); pub static ref PROD_RENDEZVOUS_SERVER: RwLock = RwLock::new(match option_env!("RENDEZVOUS_SERVER") { Some(key) if !key.is_empty() => key, @@ -210,6 +212,8 @@ pub struct Config2 { serial: i32, #[serde(default, deserialize_with = "deserialize_string")] unlock_pin: String, + #[serde(default, deserialize_with = "deserialize_string")] + trusted_devices: String, #[serde(default)] socks: Option, @@ -998,6 +1002,7 @@ impl Config { } config.password = password.into(); config.store(); + Self::clear_trusted_devices(); } pub fn get_permanent_password() -> String { @@ -1104,6 +1109,64 @@ impl Config { config.store(); } + pub fn get_trusted_devices_json() -> String { + serde_json::to_string(&Self::get_trusted_devices()).unwrap_or_default() + } + + pub fn get_trusted_devices() -> Vec { + let (devices, synced) = TRUSTED_DEVICES.read().unwrap().clone(); + if synced { + return devices; + } + let devices = CONFIG2.read().unwrap().trusted_devices.clone(); + let (devices, succ, store) = decrypt_str_or_original(&devices, PASSWORD_ENC_VERSION); + if succ { + let mut devices: Vec = + serde_json::from_str(&devices).unwrap_or_default(); + let len = devices.len(); + devices.retain(|d| !d.outdate()); + if store || devices.len() != len { + Self::set_trusted_devices(devices.clone()); + } + *TRUSTED_DEVICES.write().unwrap() = (devices.clone(), true); + devices + } else { + Default::default() + } + } + + fn set_trusted_devices(mut trusted_devices: Vec) { + trusted_devices.retain(|d| !d.outdate()); + let devices = serde_json::to_string(&trusted_devices).unwrap_or_default(); + let max_len = 1024 * 1024; + if devices.bytes().len() > max_len { + log::error!("Trusted devices too large: {}", devices.bytes().len()); + return; + } + let devices = encrypt_str_or_original(&devices, PASSWORD_ENC_VERSION, max_len); + let mut config = CONFIG2.write().unwrap(); + config.trusted_devices = devices; + config.store(); + *TRUSTED_DEVICES.write().unwrap() = (trusted_devices, true); + } + + pub fn add_trusted_device(device: TrustedDevice) { + let mut devices = Self::get_trusted_devices(); + devices.retain(|d| d.hwid != device.hwid); + devices.push(device); + Self::set_trusted_devices(devices); + } + + pub fn remove_trusted_devices(hwids: &Vec) { + let mut devices = Self::get_trusted_devices(); + devices.retain(|d| !hwids.contains(&d.hwid)); + Self::set_trusted_devices(devices); + } + + pub fn clear_trusted_devices() { + Self::set_trusted_devices(Default::default()); + } + pub fn get() -> Config { return CONFIG.read().unwrap().clone(); } @@ -1934,6 +1997,22 @@ impl Group { } } +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct TrustedDevice { + pub hwid: Bytes, + pub time: i64, + pub id: String, + pub name: String, + pub platform: String, +} + +impl TrustedDevice { + pub fn outdate(&self) -> bool { + const DAYS_90: i64 = 90 * 24 * 60 * 60 * 1000; + self.time + DAYS_90 < crate::get_time() + } +} + deserialize_default!(deserialize_string, String); deserialize_default!(deserialize_bool, bool); deserialize_default!(deserialize_i32, i32); @@ -2123,6 +2202,7 @@ pub mod keys { pub const OPTION_ENABLE_DIRECTX_CAPTURE: &str = "enable-directx-capture"; pub const OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE: &str = "enable-android-software-encoding-half-scale"; + pub const OPTION_ENABLE_TRUSTED_DEVICES: &str = "enable-trusted-devices"; // buildin options pub const OPTION_DISPLAY_NAME: &str = "display-name"; @@ -2264,6 +2344,7 @@ pub mod keys { OPTION_PRESET_ADDRESS_BOOK_TAG, OPTION_ENABLE_DIRECTX_CAPTURE, OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE, + OPTION_ENABLE_TRUSTED_DEVICES, ]; // BUILDIN_SETTINGS diff --git a/src/auth_2fa.rs b/src/auth_2fa.rs index 6945bf461..1c243bc77 100644 --- a/src/auth_2fa.rs +++ b/src/auth_2fa.rs @@ -4,7 +4,7 @@ use hbb_common::{ config::Config, get_time, password_security::{decrypt_vec_or_original, encrypt_vec_or_original}, - tokio, ResultType, + ResultType, }; use serde_derive::{Deserialize, Serialize}; use std::sync::Mutex; @@ -165,9 +165,7 @@ pub async fn send_2fa_code_to_telegram(text: &str, bot: TelegramBot) -> ResultTy pub fn get_chatid_telegram(bot_token: &str) -> ResultType> { let url = format!("https://api.telegram.org/bot{}/getUpdates", bot_token); // because caller is in tokio runtime, so we must call post_request_sync in new thread. - let handle = std::thread::spawn(move || { - crate::post_request_sync(url, "".to_owned(), "") - }); + let handle = std::thread::spawn(move || crate::post_request_sync(url, "".to_owned(), "")); let resp = handle.join().map_err(|_| anyhow!("Thread panicked"))??; let value = serde_json::from_str::(&resp).map_err(|e| anyhow!(e))?; diff --git a/src/client.rs b/src/client.rs index ec85f4807..2c5d0a339 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1329,6 +1329,7 @@ pub struct LoginConfigHandler { pub peer_info: Option, password_source: PasswordSource, // where the sent password comes from shared_password: Option, // Store the shared password + pub enable_trusted_devices: bool, } impl Deref for LoginConfigHandler { @@ -2156,6 +2157,11 @@ impl LoginConfigHandler { let my_platform = whoami::platform().to_string(); #[cfg(target_os = "android")] let my_platform = "Android".into(); + let hwid = if self.get_option("trust-this-device") == "Y" { + crate::get_hwid() + } else { + Bytes::new() + }; let mut lr = LoginRequest { username: pure_id, password: password.into(), @@ -2171,6 +2177,7 @@ impl LoginConfigHandler { ..Default::default() }) .into(), + hwid, ..Default::default() }; match self.conn_type { @@ -2827,6 +2834,12 @@ pub fn handle_login_error( interface.msgbox("re-input-password", err, "Do you want to enter again?", ""); true } else if err == LOGIN_MSG_2FA_WRONG || err == REQUIRE_2FA { + let enabled = lc.read().unwrap().get_option("trust-this-device") == "Y"; + if enabled { + lc.write() + .unwrap() + .set_option("trust-this-device".to_string(), "".to_string()); + } interface.msgbox("input-2fa", err, "", ""); true } else if LOGIN_ERROR_MAP.contains_key(err) { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index ce32b0d26..537de56ad 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1135,6 +1135,10 @@ impl Remote { } Some(message::Union::LoginResponse(lr)) => match lr.union { Some(login_response::Union::Error(err)) => { + if err == client::REQUIRE_2FA { + self.handler.lc.write().unwrap().enable_trusted_devices = + lr.enable_trusted_devices; + } if !self.handler.handle_login_error(&err) { return false; } diff --git a/src/common.rs b/src/common.rs index 108d25659..1ed9d6a8f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1494,6 +1494,15 @@ pub fn is_empty_uni_link(arg: &str) -> bool { arg[prefix.len()..].chars().all(|c| c == '/') } +pub fn get_hwid() -> Bytes { + use sha2::{Digest, Sha256}; + + let uuid = hbb_common::get_uuid(); + let mut hasher = Sha256::new(); + hasher.update(&uuid); + Bytes::from(hasher.finalize().to_vec()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d64823b8d..60f62e102 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -208,12 +208,21 @@ pub fn session_login( } } -pub fn session_send2fa(session_id: SessionID, code: String) { +pub fn session_send2fa(session_id: SessionID, code: String, trust_this_device: bool) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.send2fa(code); + session.send2fa(code, trust_this_device); } } +pub fn session_get_enable_trusted_devices(session_id: SessionID) -> SyncReturn { + let v = if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.get_enable_trusted_devices() + } else { + false + }; + SyncReturn(v) +} + pub fn session_close(session_id: SessionID) { if let Some(session) = sessions::remove_session_by_session_id(&session_id) { session.close_event_stream(session_id); @@ -2240,6 +2249,18 @@ pub fn main_check_hwcodec() { check_hwcodec() } +pub fn main_get_trusted_devices() -> String { + get_trusted_devices() +} + +pub fn main_remove_trusted_devices(json: String) { + remove_trusted_devices(&json) +} + +pub fn main_clear_trusted_devices() { + clear_trusted_devices() +} + pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usize) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.request_init_msgs(display); diff --git a/src/ipc.rs b/src/ipc.rs index be869488b..7903a942b 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -25,7 +25,9 @@ use hbb_common::{ config::{self, Config, Config2}, futures::StreamExt as _, futures_util::sink::SinkExt, - log, password_security as password, timeout, + log, password_security as password, + sodiumoxide::base64, + timeout, tokio::{ self, io::{AsyncRead, AsyncWrite}, @@ -260,6 +262,8 @@ pub enum Data { // Although the key is not neccessary, it is used to avoid hardcoding the key. WaylandScreencastRestoreToken((String, String)), HwCodecConfig(Option), + RemoveTrustedDevices(Vec), + ClearTrustedDevices, } #[tokio::main(flavor = "current_thread")] @@ -486,6 +490,8 @@ async fn handle(data: Data, stream: &mut Connection) { value = crate::audio_service::get_voice_call_input_device(); } else if name == "unlock-pin" { value = Some(Config::get_unlock_pin()); + } else if name == "trusted-devices" { + value = Some(Config::get_trusted_devices_json()); } else { value = None; } @@ -638,6 +644,12 @@ async fn handle(data: Data, stream: &mut Connection) { ); } } + Data::RemoveTrustedDevices(v) => { + Config::remove_trusted_devices(&v); + } + Data::ClearTrustedDevices => { + Config::clear_trusted_devices(); + } _ => {} } } @@ -866,6 +878,17 @@ pub async fn set_config_async(name: &str, value: String) -> ResultType<()> { Ok(()) } +#[tokio::main(flavor = "current_thread")] +pub async fn set_data(data: &Data) -> ResultType<()> { + set_data_async(data).await +} + +pub async fn set_data_async(data: &Data) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(data).await?; + Ok(()) +} + #[tokio::main(flavor = "current_thread")] pub async fn set_config(name: &str, value: String) -> ResultType<()> { set_config_async(name, value).await @@ -926,6 +949,30 @@ pub fn get_unlock_pin() -> String { } } +#[cfg(feature = "flutter")] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn get_trusted_devices() -> String { + if let Ok(Some(v)) = get_config("trusted-devices") { + v + } else { + Config::get_trusted_devices_json() + } +} + +#[cfg(feature = "flutter")] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn remove_trusted_devices(hwids: Vec) { + Config::remove_trusted_devices(&hwids); + allow_err!(set_data(&Data::RemoveTrustedDevices(hwids))); +} + +#[cfg(feature = "flutter")] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn clear_trusted_devices() { + Config::clear_trusted_devices(); + allow_err!(set_data(&Data::ClearTrustedDevices)); +} + pub fn get_id() -> String { if let Ok(Some(v)) = get_config("id") { // update salt also, so that next time reinstallation not causing first-time auto-login failure diff --git a/src/lang/ar.rs b/src/lang/ar.rs index d1da83ec5..cf4790e43 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "الوثوق بهذا الجهاز"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index a294fcae0..81957bdc6 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Даверыць гэтую прыладу"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 9eb0ca63f..42d3752b6 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Доверете се на това устройство"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 4721ed19c..8ee0058be 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Confia en aquest dispositiu"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 6481761fd..a50568ec4 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "不少于{}个字符"), ("Wrong PIN", "PIN 码错误"), ("Set PIN", "设置 PIN 码"), + ("Enable trusted devices", "启用信任设备"), + ("Manage trusted devices", "管理信任设备"), + ("Trust this device", "信任此设备"), + ("Platform", "平台"), + ("Days remaining", "剩余天数"), + ("enable-trusted-devices-tip", "允许受信任的设备跳过 2FA 验证"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 2caf00e5d..7c1c10a89 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Důvěřovat tomuto zařízení"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 44d19dd46..79f470461 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Husk denne enhed"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index c31cc9d99..c04550e25 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Erfordert mindestens {} Zeichen"), ("Wrong PIN", "Falsche PIN"), ("Set PIN", "PIN festlegen"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Diesem Gerät vertrauen"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 2f929f3b1..4af902b90 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Εμπιστεύομαι αυτή την συσκευή"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 638c1a608..917422d0d 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -232,6 +232,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("cancel-2fa-confirm-tip", "Are you sure you want to cancel 2FA?"), ("cancel-bot-confirm-tip", "Are you sure you want to cancel Telegram bot?"), ("About RustDesk", ""), - ("network_error_tip", "Please check your network connection, then click retry.") + ("network_error_tip", "Please check your network connection, then click retry."), + ("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 70d73bfc6..27ccf9945 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index da6c2b4ca..b00bc5ce2 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Confiar en este dispositivo"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 74c30b076..967eba421 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 55a9dcbfb..307b3aff3 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Gailu honetaz fidatu"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 8166e0f92..b16e1439e 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "حداقل به {} کاراکترها نیاز دارد"), ("Wrong PIN", "پین اشتباه است"), ("Set PIN", "پین را تنظیم کنید"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "به این دستگاه اعتماد کنید"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index c64aa9ee2..010b8a4b1 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Faire confiance à cet appareil"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 4e21c204d..3dd423da0 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index f221ebcee..392a6e825 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Vjeruj ovom uređaju"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 40fbf2f76..585a6a711 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index eab2e1a36..8c1c6c9d0 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Izinkan perangkat ini"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index d50b963b5..3cecd6805 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Richiede almeno {} caratteri"), ("Wrong PIN", "PIN errato"), ("Set PIN", "Imposta PIN"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Registra questo dispositivo come attendibile"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 8c8c71944..471f796ed 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "このデバイスを信頼する"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 65db10aec..cd15163c2 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "이 장치 신뢰"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 7545bff40..74205198a 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index e659014e5..f5deb81e4 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Pasitikėk šiuo įrenginiu"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 3296a51ae..708724eb2 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Uzticēties šai ierīcei"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 1afa22d99..910d082bc 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Husk denne enheten"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 187611393..39435ac9c 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Vereist minstens {} tekens"), ("Wrong PIN", "Verkeerde PIN-code"), ("Set PIN", "PIN-code instellen"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Vertrouw dit apparaat"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 5d04d76c5..de6f9334e 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Dodaj to urządzenie do zaufanych"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 965d934df..d1b5d0adc 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 7bf00d207..8745269ce 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", "PIN Errado"), ("Set PIN", "Definir PIN"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Confiar neste dispositivo"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 639f0a29c..d0ebb7b56 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Acest dispozitiv este de încredere"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index f3fe46f3a..94f713850 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Требуется не менее {} символов"), ("Wrong PIN", "Неправильный PIN-код"), ("Set PIN", "Установить PIN-код"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Доверенное устройство"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 816106c57..466631ac1 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Dôverovať tomuto zariadeniu"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 1dd8280c6..92301f3fa 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index f11d48560..f2cf322a9 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 2bc72e6e3..f5725d1e7 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index ed7752e26..52d547fe3 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Lita på denna enhet"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 7e57544a8..fa89e2bdf 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 6789a4573..5fe176a8c 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "เชื่อถืออุปกรณ์นี้"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index e9b3d6cf2..d0dc484c7 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", ""), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 0a4b26144..a9ffa45d6 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "信任此裝置"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 3d031a65e..a7f37014d 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Потрібно щонайменше {} символів"), ("Wrong PIN", "Неправильний PIN-код"), ("Set PIN", "Встановити PIN-код"), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Довірений пристрій"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 095364362..dadc0a154 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", ""), ("Wrong PIN", ""), ("Set PIN", ""), + ("Enable trusted devices", ""), + ("Manage trusted devices", ""), + ("Trust this device", "Tin thiết bị này"), + ("Platform", ""), + ("Days remaining", ""), + ("enable-trusted-devices-tip", ""), ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index bc8c4ef0e..929d040e2 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -27,7 +27,7 @@ use hbb_common::platform::linux::run_cmds; #[cfg(target_os = "android")] use hbb_common::protobuf::EnumOrUnknown; use hbb_common::{ - config::{self, Config}, + config::{self, Config, TrustedDevice}, fs::{self, can_enable_overwrite_detection}, futures::{SinkExt, StreamExt}, get_time, get_version_number, @@ -1482,6 +1482,9 @@ impl Connection { let mut msg_out = Message::new(); let mut res = LoginResponse::new(); res.set_error(err.to_string()); + if err.to_string() == crate::client::REQUIRE_2FA { + res.enable_trusted_devices = Self::enable_trusted_devices(); + } msg_out.set_login_response(res); self.send(msg_out).await; } @@ -1623,11 +1626,32 @@ impl Connection { } } + #[inline] + fn enable_trusted_devices() -> bool { + config::option2bool( + config::keys::OPTION_ENABLE_TRUSTED_DEVICES, + &Config::get_option(config::keys::OPTION_ENABLE_TRUSTED_DEVICES), + ) + } + async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) { self.lr = lr.clone(); if let Some(o) = lr.option.as_ref() { self.options_in_login = Some(o.clone()); } + if self.require_2fa.is_some() && !lr.hwid.is_empty() && Self::enable_trusted_devices() { + let devices = Config::get_trusted_devices(); + if let Some(device) = devices.iter().find(|d| d.hwid == lr.hwid) { + if !device.outdate() + && device.id == lr.my_id + && device.name == lr.my_name + && device.platform == lr.my_platform + { + log::info!("2FA bypassed by trusted devices"); + self.require_2fa = None; + } + } + } self.video_ack_required = lr.video_ack_required; } @@ -1841,6 +1865,15 @@ impl Connection { }, ); } + if !tfa.hwid.is_empty() && Self::enable_trusted_devices() { + Config::add_trusted_device(TrustedDevice { + hwid: tfa.hwid, + time: hbb_common::get_time(), + id: self.lr.my_id.clone(), + name: self.lr.my_name.clone(), + platform: self.lr.my_platform.clone(), + }); + } } else { self.update_failure(failure, false, 1); self.send_login_error(crate::client::LOGIN_MSG_2FA_WRONG) diff --git a/src/ui/common.tis b/src/ui/common.tis index 0f154a74c..b6d2b8ee2 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -268,7 +268,7 @@ function msgbox(type, title, content, link="", callback=null, height=180, width= view.close(); return; } - handler.send2fa(res.code); + handler.send2fa(res.code, res.trust_this_device || false); msgbox("connecting", "Connecting...", "Logging in..."); }; } else if (type == "session-login" || type == "session-re-login") { diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis index a8fa79ad4..34f6b0443 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.tis @@ -66,9 +66,11 @@ class MsgboxComponent: Reactor.Component { } function get2faContent() { + var enable_trusted_devices = handler.get_enable_trusted_devices(); return
{translate('enter-2fa-title')}
+ {enable_trusted_devices ?
{translate('Trust this device')}
: ""}
; } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 93a796f4b..0ad84e8ed 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -433,7 +433,8 @@ impl sciter::EventHandler for SciterSession { fn is_port_forward(); fn is_rdp(); fn login(String, String, String, bool); - fn send2fa(String); + fn send2fa(String, bool); + fn get_enable_trusted_devices(); fn new_rdp(); fn send_mouse(i32, i32, i32, bool, bool, bool, bool); fn enter(String); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index c39a58068..9c3864f0c 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1471,3 +1471,28 @@ pub fn set_unlock_pin(pin: String) -> String { Err(err) => err.to_string(), } } + +#[cfg(feature = "flutter")] +pub fn get_trusted_devices() -> String { + #[cfg(any(target_os = "android", target_os = "ios"))] + return Config::get_trusted_devices_json(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + return ipc::get_trusted_devices(); +} + +#[cfg(feature = "flutter")] +pub fn remove_trusted_devices(json: &str) { + let hwids = serde_json::from_str::>(json).unwrap_or_default(); + #[cfg(any(target_os = "android", target_os = "ios"))] + Config::remove_trusted_devices(&hwids); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + ipc::remove_trusted_devices(hwids); +} + +#[cfg(feature = "flutter")] +pub fn clear_trusted_devices() { + #[cfg(any(target_os = "android", target_os = "ios"))] + Config::clear_trusted_devices(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + ipc::clear_trusted_devices(); +} diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 02fdf1caa..0e030aa8b 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1156,15 +1156,29 @@ impl Session { self.send(Data::Login((os_username, os_password, password, remember))); } - pub fn send2fa(&self, code: String) { + pub fn send2fa(&self, code: String, trust_this_device: bool) { let mut msg_out = Message::new(); + let hwid = if trust_this_device { + crate::get_hwid() + } else { + Bytes::new() + }; + self.lc.write().unwrap().set_option( + "trust-this-device".to_string(), + if trust_this_device { "Y" } else { "" }.to_string(), + ); msg_out.set_auth_2fa(Auth2FA { code, + hwid, ..Default::default() }); self.send(Data::Message(msg_out)); } + pub fn get_enable_trusted_devices(&self) -> bool { + self.lc.read().unwrap().enable_trusted_devices + } + pub fn new_rdp(&self) { self.send(Data::NewRDP); }