trust this device to skip 2fa (#9012)

* trust this device to skip 2fa

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Update connection.rs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
21pages 2024-08-12 18:08:33 +08:00 committed by GitHub
parent 57834840b8
commit 1729ee337f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 845 additions and 22 deletions

View File

@ -1831,6 +1831,7 @@ void changeBot({Function()? callback}) async {
void change2fa({Function()? callback}) async { void change2fa({Function()? callback}) async {
if (bind.mainHasValid2FaSync()) { if (bind.mainHasValid2FaSync()) {
await bind.mainSetOption(key: "2fa", value: ""); await bind.mainSetOption(key: "2fa", value: "");
await bind.mainClearTrustedDevices();
callback?.call(); callback?.call();
return; return;
} }
@ -1898,6 +1899,7 @@ void enter2FaDialog(
SessionID sessionId, OverlayDialogManager dialogManager) async { SessionID sessionId, OverlayDialogManager dialogManager) async {
final controller = TextEditingController(); final controller = TextEditingController();
final RxBool submitReady = false.obs; final RxBool submitReady = false.obs;
final RxBool trustThisDevice = false.obs;
dialogManager.dismissAll(); dialogManager.dismissAll();
dialogManager.show((setState, close, context) { dialogManager.show((setState, close, context) {
@ -1907,7 +1909,7 @@ void enter2FaDialog(
} }
submit() { submit() {
gFFI.send2FA(sessionId, controller.text.trim()); gFFI.send2FA(sessionId, controller.text.trim(), trustThisDevice.value);
close(); close();
dialogManager.showLoading(translate('Logging in...'), dialogManager.showLoading(translate('Logging in...'),
onCancel: closeConnection); onCancel: closeConnection);
@ -1921,9 +1923,27 @@ void enter2FaDialog(
onChanged: () => submitReady.value = codeField.isReady, 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( return CustomAlertDialog(
title: Text(translate('enter-2fa-title')), title: Text(translate('enter-2fa-title')),
content: codeField, content: Column(
children: [
codeField,
if (bind.sessionGetEnableTrustedDevices(sessionId: sessionId))
trustField,
],
),
actions: [ actions: [
dialogButton('Cancel', dialogButton('Cancel',
onPressed: cancel, onPressed: cancel,
@ -2313,3 +2333,157 @@ void checkUnlockPinDialog(String correctPin, Function() passCallback) {
); );
}); });
} }
void confrimDeleteTrustedDevicesDialog(
RxList<TrustedDevice> trustedDevices, RxList<Uint8List> 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<TrustedDevice> trustedDevices = (await TrustedDevice.get()).obs;
RxList<Uint8List> 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<String, dynamic> json) {
final hwidList = json['hwid'] as List<dynamic>;
hwid = Uint8List.fromList(hwidList.cast<int>());
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<List<TrustedDevice>> get() async {
final List<TrustedDevice> 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<TrustedDevice> devices, RxList<Uint8List> 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(),
)),
);
}

View File

@ -136,6 +136,7 @@ const String kOptionAllowRemoveWallpaper = "allow-remove-wallpaper";
const String kOptionStopService = "stop-service"; const String kOptionStopService = "stop-service";
const String kOptionDirectxCapture = "enable-directx-capture"; const String kOptionDirectxCapture = "enable-directx-capture";
const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification"; const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification";
const String kOptionEnableTrustedDevices = "enable-trusted-devices";
// buildin opitons // buildin opitons
const String kOptionHideServerSetting = "hide-server-settings"; const String kOptionHideServerSetting = "hide-server-settings";

View File

@ -783,8 +783,33 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
onChangedBot(!hasBot.value); onChangedBot(!hasBot.value);
}, },
).marginOnly(left: _kCheckBoxLeftMargin + 30); ).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( return Column(
children: [tfa, bot], children: [tfa, bot, trust],
); );
} }

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
@ -87,6 +88,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
var _hideServer = false; var _hideServer = false;
var _hideProxy = false; var _hideProxy = false;
var _hideNetwork = false; var _hideNetwork = false;
var _enableTrustedDevices = false;
_SettingsState() { _SettingsState() {
_enableAbr = option2bool( _enableAbr = option2bool(
@ -113,6 +115,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_hideProxy = bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; _hideProxy = bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
_hideNetwork = _hideNetwork =
bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) == 'Y'; bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) == 'Y';
_enableTrustedDevices = mainGetBoolOptionSync(kOptionEnableTrustedDevices);
} }
@override @override
@ -243,18 +246,57 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
], ],
)); ));
final List<AbstractSettingsTile> enhancementsTiles = []; final List<AbstractSettingsTile> enhancementsTiles = [];
final List<AbstractSettingsTile> shareScreenTiles = [ final enable2fa = bind.mainHasValid2FaSync();
final List<AbstractSettingsTile> tfaTiles = [
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('enable-2fa-title')), title: Text(translate('enable-2fa-title')),
initialValue: bind.mainHasValid2FaSync(), initialValue: enable2fa,
onToggle: (_) async { onToggle: (v) async {
update() async { update() async {
setState(() {}); setState(() {});
} }
if (v == false) {
CommonConfirmDialog(
gFFI.dialogManager, translate('cancel-2fa-confirm-tip'), () {
change2fa(callback: update); 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<AbstractSettingsTile> shareScreenTiles = [
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('Deny LAN discovery')), title: Text(translate('Deny LAN discovery')),
initialValue: _denyLANDiscovery, initialValue: _denyLANDiscovery,
@ -642,6 +684,11 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
), ),
], ],
), ),
if (isAndroid &&
!disabledSettings &&
!outgoingOnly &&
!hideSecuritySettings)
SettingsSection(title: Text('2FA'), tiles: tfaTiles),
if (isAndroid && if (isAndroid &&
!disabledSettings && !disabledSettings &&
!outgoingOnly && !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<TrustedDevice> trustedDevices = RxList.empty(growable: true);
RxList<Uint8List> 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<TrustedDevice>;
trustedDevices = devices.obs;
return trustedDevicesTable(trustedDevices, selectedDevices);
}),
);
}
}
class _RadioEntry { class _RadioEntry {
final String label; final String label;
final String value; final String value;

View File

@ -2611,8 +2611,9 @@ class FFI {
remember: remember); remember: remember);
} }
void send2FA(SessionID sessionId, String code) { void send2FA(SessionID sessionId, String code, bool trustThisDevice) {
bind.sessionSend2Fa(sessionId: sessionId, code: code); bind.sessionSend2Fa(
sessionId: sessionId, code: code, trustThisDevice: trustThisDevice);
} }
/// Close the remote session. /// Close the remote session.

View File

@ -142,7 +142,10 @@ class RustdeskImpl {
} }
Future<void> sessionSend2Fa( Future<void> 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])); return Future(() => js.context.callMethod('setByName', ['send_2fa', code]));
} }
@ -1630,5 +1633,22 @@ class RustdeskImpl {
throw UnimplementedError(); throw UnimplementedError();
} }
bool sessionGetEnableTrustedDevices(
{required UuidValue sessionId, dynamic hint}) {
throw UnimplementedError();
}
Future<String> mainGetTrustedDevices({dynamic hint}) {
throw UnimplementedError();
}
Future<void> mainRemoveTrustedDevices({required String json, dynamic hint}) {
throw UnimplementedError();
}
Future<void> mainClearTrustedDevices({dynamic hint}) {
throw UnimplementedError();
}
void dispose() {} void dispose() {}
} }

View File

@ -82,10 +82,12 @@ message LoginRequest {
string version = 11; string version = 11;
OSLogin os_login = 12; OSLogin os_login = 12;
string my_platform = 13; string my_platform = 13;
bytes hwid = 14;
} }
message Auth2FA { message Auth2FA {
string code = 1; string code = 1;
bytes hwid = 2;
} }
message ChatMessage { string text = 1; } message ChatMessage { string text = 1; }
@ -137,6 +139,7 @@ message LoginResponse {
string error = 1; string error = 1;
PeerInfo peer_info = 2; PeerInfo peer_info = 2;
} }
bool enable_trusted_devices = 3;
} }
message TouchScaleUpdate { message TouchScaleUpdate {

View File

@ -10,6 +10,7 @@ use std::{
}; };
use anyhow::Result; use anyhow::Result;
use bytes::Bytes;
use rand::Rng; use rand::Rng;
use regex::Regex; use regex::Regex;
use serde as de; use serde as de;
@ -52,6 +53,7 @@ lazy_static::lazy_static! {
static ref CONFIG: RwLock<Config> = RwLock::new(Config::load()); static ref CONFIG: RwLock<Config> = RwLock::new(Config::load());
static ref CONFIG2: RwLock<Config2> = RwLock::new(Config2::load()); static ref CONFIG2: RwLock<Config2> = RwLock::new(Config2::load());
static ref LOCAL_CONFIG: RwLock<LocalConfig> = RwLock::new(LocalConfig::load()); static ref LOCAL_CONFIG: RwLock<LocalConfig> = RwLock::new(LocalConfig::load());
static ref TRUSTED_DEVICES: RwLock<(Vec<TrustedDevice>, bool)> = Default::default();
static ref ONLINE: Mutex<HashMap<String, i64>> = Default::default(); static ref ONLINE: Mutex<HashMap<String, i64>> = Default::default();
pub static ref PROD_RENDEZVOUS_SERVER: RwLock<String> = RwLock::new(match option_env!("RENDEZVOUS_SERVER") { pub static ref PROD_RENDEZVOUS_SERVER: RwLock<String> = RwLock::new(match option_env!("RENDEZVOUS_SERVER") {
Some(key) if !key.is_empty() => key, Some(key) if !key.is_empty() => key,
@ -210,6 +212,8 @@ pub struct Config2 {
serial: i32, serial: i32,
#[serde(default, deserialize_with = "deserialize_string")] #[serde(default, deserialize_with = "deserialize_string")]
unlock_pin: String, unlock_pin: String,
#[serde(default, deserialize_with = "deserialize_string")]
trusted_devices: String,
#[serde(default)] #[serde(default)]
socks: Option<Socks5Server>, socks: Option<Socks5Server>,
@ -998,6 +1002,7 @@ impl Config {
} }
config.password = password.into(); config.password = password.into();
config.store(); config.store();
Self::clear_trusted_devices();
} }
pub fn get_permanent_password() -> String { pub fn get_permanent_password() -> String {
@ -1104,6 +1109,64 @@ impl Config {
config.store(); 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<TrustedDevice> {
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<TrustedDevice> =
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<TrustedDevice>) {
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<Bytes>) {
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 { pub fn get() -> Config {
return CONFIG.read().unwrap().clone(); 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_string, String);
deserialize_default!(deserialize_bool, bool); deserialize_default!(deserialize_bool, bool);
deserialize_default!(deserialize_i32, i32); 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_DIRECTX_CAPTURE: &str = "enable-directx-capture";
pub const OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE: &str = pub const OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE: &str =
"enable-android-software-encoding-half-scale"; "enable-android-software-encoding-half-scale";
pub const OPTION_ENABLE_TRUSTED_DEVICES: &str = "enable-trusted-devices";
// buildin options // buildin options
pub const OPTION_DISPLAY_NAME: &str = "display-name"; pub const OPTION_DISPLAY_NAME: &str = "display-name";
@ -2264,6 +2344,7 @@ pub mod keys {
OPTION_PRESET_ADDRESS_BOOK_TAG, OPTION_PRESET_ADDRESS_BOOK_TAG,
OPTION_ENABLE_DIRECTX_CAPTURE, OPTION_ENABLE_DIRECTX_CAPTURE,
OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE, OPTION_ENABLE_ANDROID_SOFTWARE_ENCODING_HALF_SCALE,
OPTION_ENABLE_TRUSTED_DEVICES,
]; ];
// BUILDIN_SETTINGS // BUILDIN_SETTINGS

View File

@ -4,7 +4,7 @@ use hbb_common::{
config::Config, config::Config,
get_time, get_time,
password_security::{decrypt_vec_or_original, encrypt_vec_or_original}, password_security::{decrypt_vec_or_original, encrypt_vec_or_original},
tokio, ResultType, ResultType,
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::sync::Mutex; 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<Option<String>> { pub fn get_chatid_telegram(bot_token: &str) -> ResultType<Option<String>> {
let url = format!("https://api.telegram.org/bot{}/getUpdates", bot_token); 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. // because caller is in tokio runtime, so we must call post_request_sync in new thread.
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || crate::post_request_sync(url, "".to_owned(), ""));
crate::post_request_sync(url, "".to_owned(), "")
});
let resp = handle.join().map_err(|_| anyhow!("Thread panicked"))??; let resp = handle.join().map_err(|_| anyhow!("Thread panicked"))??;
let value = serde_json::from_str::<serde_json::Value>(&resp).map_err(|e| anyhow!(e))?; let value = serde_json::from_str::<serde_json::Value>(&resp).map_err(|e| anyhow!(e))?;

View File

@ -1329,6 +1329,7 @@ pub struct LoginConfigHandler {
pub peer_info: Option<PeerInfo>, pub peer_info: Option<PeerInfo>,
password_source: PasswordSource, // where the sent password comes from password_source: PasswordSource, // where the sent password comes from
shared_password: Option<String>, // Store the shared password shared_password: Option<String>, // Store the shared password
pub enable_trusted_devices: bool,
} }
impl Deref for LoginConfigHandler { impl Deref for LoginConfigHandler {
@ -2156,6 +2157,11 @@ impl LoginConfigHandler {
let my_platform = whoami::platform().to_string(); let my_platform = whoami::platform().to_string();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
let my_platform = "Android".into(); 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 { let mut lr = LoginRequest {
username: pure_id, username: pure_id,
password: password.into(), password: password.into(),
@ -2171,6 +2177,7 @@ impl LoginConfigHandler {
..Default::default() ..Default::default()
}) })
.into(), .into(),
hwid,
..Default::default() ..Default::default()
}; };
match self.conn_type { 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?", ""); interface.msgbox("re-input-password", err, "Do you want to enter again?", "");
true true
} else if err == LOGIN_MSG_2FA_WRONG || err == REQUIRE_2FA { } 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, "", ""); interface.msgbox("input-2fa", err, "", "");
true true
} else if LOGIN_ERROR_MAP.contains_key(err) { } else if LOGIN_ERROR_MAP.contains_key(err) {

View File

@ -1135,6 +1135,10 @@ impl<T: InvokeUiSession> Remote<T> {
} }
Some(message::Union::LoginResponse(lr)) => match lr.union { Some(message::Union::LoginResponse(lr)) => match lr.union {
Some(login_response::Union::Error(err)) => { 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) { if !self.handler.handle_login_error(&err) {
return false; return false;
} }

View File

@ -1494,6 +1494,15 @@ pub fn is_empty_uni_link(arg: &str) -> bool {
arg[prefix.len()..].chars().all(|c| c == '/') 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -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) { 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<bool> {
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) { pub fn session_close(session_id: SessionID) {
if let Some(session) = sessions::remove_session_by_session_id(&session_id) { if let Some(session) = sessions::remove_session_by_session_id(&session_id) {
session.close_event_stream(session_id); session.close_event_stream(session_id);
@ -2240,6 +2249,18 @@ pub fn main_check_hwcodec() {
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) { 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) { if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.request_init_msgs(display); session.request_init_msgs(display);

View File

@ -25,7 +25,9 @@ use hbb_common::{
config::{self, Config, Config2}, config::{self, Config, Config2},
futures::StreamExt as _, futures::StreamExt as _,
futures_util::sink::SinkExt, futures_util::sink::SinkExt,
log, password_security as password, timeout, log, password_security as password,
sodiumoxide::base64,
timeout,
tokio::{ tokio::{
self, self,
io::{AsyncRead, AsyncWrite}, io::{AsyncRead, AsyncWrite},
@ -260,6 +262,8 @@ pub enum Data {
// Although the key is not neccessary, it is used to avoid hardcoding the key. // Although the key is not neccessary, it is used to avoid hardcoding the key.
WaylandScreencastRestoreToken((String, String)), WaylandScreencastRestoreToken((String, String)),
HwCodecConfig(Option<String>), HwCodecConfig(Option<String>),
RemoveTrustedDevices(Vec<Bytes>),
ClearTrustedDevices,
} }
#[tokio::main(flavor = "current_thread")] #[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(); value = crate::audio_service::get_voice_call_input_device();
} else if name == "unlock-pin" { } else if name == "unlock-pin" {
value = Some(Config::get_unlock_pin()); value = Some(Config::get_unlock_pin());
} else if name == "trusted-devices" {
value = Some(Config::get_trusted_devices_json());
} else { } else {
value = None; 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(()) 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")] #[tokio::main(flavor = "current_thread")]
pub async fn set_config(name: &str, value: String) -> ResultType<()> { pub async fn set_config(name: &str, value: String) -> ResultType<()> {
set_config_async(name, value).await 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<Bytes>) {
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 { pub fn get_id() -> String {
if let Ok(Some(v)) = get_config("id") { if let Ok(Some(v)) = get_config("id") {
// update salt also, so that next time reinstallation not causing first-time auto-login failure // update salt also, so that next time reinstallation not causing first-time auto-login failure

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "الوثوق بهذا الجهاز"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "Даверыць гэтую прыладу"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "Доверете се на това устройство"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "不少于{}个字符"), ("Requires at least {} characters", "不少于{}个字符"),
("Wrong PIN", "PIN 码错误"), ("Wrong PIN", "PIN 码错误"),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "Erfordert mindestens {} Zeichen"), ("Requires at least {} characters", "Erfordert mindestens {} Zeichen"),
("Wrong PIN", "Falsche PIN"), ("Wrong PIN", "Falsche PIN"),
("Set PIN", "PIN festlegen"), ("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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "Εμπιστεύομαι αυτή την συσκευή"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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-2fa-confirm-tip", "Are you sure you want to cancel 2FA?"),
("cancel-bot-confirm-tip", "Are you sure you want to cancel Telegram bot?"), ("cancel-bot-confirm-tip", "Are you sure you want to cancel Telegram bot?"),
("About RustDesk", ""), ("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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "حداقل به {} کاراکترها نیاز دارد"), ("Requires at least {} characters", "حداقل به {} کاراکترها نیاز دارد"),
("Wrong PIN", "پین اشتباه است"), ("Wrong PIN", "پین اشتباه است"),
("Set PIN", "پین را تنظیم کنید"), ("Set PIN", "پین را تنظیم کنید"),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "به این دستگاه اعتماد کنید"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "Richiede almeno {} caratteri"), ("Requires at least {} characters", "Richiede almeno {} caratteri"),
("Wrong PIN", "PIN errato"), ("Wrong PIN", "PIN errato"),
("Set PIN", "Imposta PIN"), ("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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "このデバイスを信頼する"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "이 장치 신뢰"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "Vereist minstens {} tekens"), ("Requires at least {} characters", "Vereist minstens {} tekens"),
("Wrong PIN", "Verkeerde PIN-code"), ("Wrong PIN", "Verkeerde PIN-code"),
("Set PIN", "PIN-code instellen"), ("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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", "PIN Errado"), ("Wrong PIN", "PIN Errado"),
("Set PIN", "Definir PIN"), ("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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "Требуется не менее {} символов"), ("Requires at least {} characters", "Требуется не менее {} символов"),
("Wrong PIN", "Неправильный PIN-код"), ("Wrong PIN", "Неправильный PIN-код"),
("Set PIN", "Установить PIN-код"), ("Set PIN", "Установить PIN-код"),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "Доверенное устройство"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "เชื่อถืออุปกรณ์นี้"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", ""),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set PIN", ""), ("Set PIN", ""),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "信任此裝置"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", "Потрібно щонайменше {} символів"), ("Requires at least {} characters", "Потрібно щонайменше {} символів"),
("Wrong PIN", "Неправильний PIN-код"), ("Wrong PIN", "Неправильний PIN-код"),
("Set PIN", "Встановити PIN-код"), ("Set PIN", "Встановити PIN-код"),
("Enable trusted devices", ""),
("Manage trusted devices", ""),
("Trust this device", "Довірений пристрій"),
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -636,5 +636,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Requires at least {} characters", ""), ("Requires at least {} characters", ""),
("Wrong PIN", ""), ("Wrong PIN", ""),
("Set 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(); ].iter().cloned().collect();
} }

View File

@ -27,7 +27,7 @@ use hbb_common::platform::linux::run_cmds;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use hbb_common::protobuf::EnumOrUnknown; use hbb_common::protobuf::EnumOrUnknown;
use hbb_common::{ use hbb_common::{
config::{self, Config}, config::{self, Config, TrustedDevice},
fs::{self, can_enable_overwrite_detection}, fs::{self, can_enable_overwrite_detection},
futures::{SinkExt, StreamExt}, futures::{SinkExt, StreamExt},
get_time, get_version_number, get_time, get_version_number,
@ -1482,6 +1482,9 @@ impl Connection {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
let mut res = LoginResponse::new(); let mut res = LoginResponse::new();
res.set_error(err.to_string()); 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); msg_out.set_login_response(res);
self.send(msg_out).await; 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) { async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
self.lr = lr.clone(); self.lr = lr.clone();
if let Some(o) = lr.option.as_ref() { if let Some(o) = lr.option.as_ref() {
self.options_in_login = Some(o.clone()); 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; 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 { } else {
self.update_failure(failure, false, 1); self.update_failure(failure, false, 1);
self.send_login_error(crate::client::LOGIN_MSG_2FA_WRONG) self.send_login_error(crate::client::LOGIN_MSG_2FA_WRONG)

View File

@ -268,7 +268,7 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
view.close(); view.close();
return; return;
} }
handler.send2fa(res.code); handler.send2fa(res.code, res.trust_this_device || false);
msgbox("connecting", "Connecting...", "Logging in..."); msgbox("connecting", "Connecting...", "Logging in...");
}; };
} else if (type == "session-login" || type == "session-re-login") { } else if (type == "session-login" || type == "session-re-login") {

View File

@ -66,9 +66,11 @@ class MsgboxComponent: Reactor.Component {
} }
function get2faContent() { function get2faContent() {
var enable_trusted_devices = handler.get_enable_trusted_devices();
return <div .form> return <div .form>
<div>{translate('enter-2fa-title')}</div> <div>{translate('enter-2fa-title')}</div>
<div .code><input name='code' type='text' .outline-focus /></div> <div .code><input name='code' type='text' .outline-focus /></div>
{enable_trusted_devices ? <div><button|checkbox(trust_this_device) {ts}>{translate('Trust this device')}</button></div> : ""}
</div>; </div>;
} }

View File

@ -433,7 +433,8 @@ impl sciter::EventHandler for SciterSession {
fn is_port_forward(); fn is_port_forward();
fn is_rdp(); fn is_rdp();
fn login(String, String, String, bool); fn login(String, String, String, bool);
fn send2fa(String); fn send2fa(String, bool);
fn get_enable_trusted_devices();
fn new_rdp(); fn new_rdp();
fn send_mouse(i32, i32, i32, bool, bool, bool, bool); fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
fn enter(String); fn enter(String);

View File

@ -1471,3 +1471,28 @@ pub fn set_unlock_pin(pin: String) -> String {
Err(err) => err.to_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::<Vec<Bytes>>(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();
}

View File

@ -1156,15 +1156,29 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Login((os_username, os_password, password, remember))); 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 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 { msg_out.set_auth_2fa(Auth2FA {
code, code,
hwid,
..Default::default() ..Default::default()
}); });
self.send(Data::Message(msg_out)); 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) { pub fn new_rdp(&self) {
self.send(Data::NewRDP); self.send(Data::NewRDP);
} }