diff --git a/Cargo.lock b/Cargo.lock index 37855d569..13bb0f4c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base64" version = "0.21.5" @@ -1135,6 +1141,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + [[package]] name = "convert_case" version = "0.4.0" @@ -5427,6 +5439,7 @@ dependencies = [ "system_shutdown", "tao", "tauri-winrt-notification", + "totp-rs", "tray-icon", "url", "users 0.11.0", @@ -6503,6 +6516,22 @@ dependencies = [ "winnow", ] +[[package]] +name = "totp-rs" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3504f96adf86d28e7eb16fa236a7951ec72c15ee100d1b5318e225944bc8cb" +dependencies = [ + "base32", + "constant_time_eq 0.2.6", + "hmac", + "rand 0.8.5", + "sha1", + "sha2", + "url", + "urlencoding", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -6701,6 +6730,12 @@ dependencies = [ "serde 1.0.190", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "users" version = "0.10.0" @@ -7726,7 +7761,7 @@ dependencies = [ "aes", "byteorder", "bzip2", - "constant_time_eq", + "constant_time_eq 0.1.5", "crc32fast", "crossbeam-utils", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 4fa47adf7..dc765c505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ libloading = "0.8" fon = "0.6" zip = "0.6" shutdown_hooks = "0.1" +totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] } [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] cpal = "0.15" diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 6a72cb9d5..95877cbfd 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -7,6 +7,7 @@ import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:get/get.dart'; +import 'package:qr_flutter/qr_flutter.dart'; import '../../common.dart'; import '../../models/model.dart'; @@ -1501,3 +1502,124 @@ void renameDialog( ); }); } + +void change2fa({Function()? callback}) async { + if (bind.mainHasValid2FaSync()) { + await bind.mainSetOption(key: "2fa", value: ""); + callback?.call(); + return; + } + var new2fa = (await bind.mainGenerate2Fa()); + final secretRegex = RegExp(r'secret=([^&]+)'); + final secret = secretRegex.firstMatch(new2fa)?.group(1); + var msg = ''.obs; + final RxString code = "".obs; + gFFI.dialogManager.show((setState, close, context) { + return CustomAlertDialog( + title: Text(translate("enable-2fa-title")), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText(translate("enable-2fa-desc"), + style: TextStyle(fontSize: 12)) + .marginOnly(bottom: 12), + SizedBox( + width: 160, + height: 160, + child: QrImageView( + backgroundColor: Colors.white, + data: new2fa, + version: QrVersions.auto, + size: 160, + gapless: false, + )).marginOnly(bottom: 6), + SelectableText(secret ?? '', style: TextStyle(fontSize: 12)) + .marginOnly(bottom: 12), + Row(children: [ + Expanded( + child: Obx(() => TextField( + decoration: InputDecoration( + errorText: + msg.value.isEmpty ? null : translate(msg.value), + hintText: translate("Verification code")), + onChanged: (value) { + code.value = value; + msg.value = ''; + }, + autofocus: true))) + ]), + ], + ), + actions: [ + dialogButton("Cancel", onPressed: close, isOutline: true), + Obx(() => dialogButton( + "OK", + onPressed: code.value.trim().length == 6 + ? () async { + if (await bind.mainVerify2Fa(code: code.value.trim())) { + callback?.call(); + close(); + } else { + msg.value = translate('wrong-2fa-code'); + } + } + : null, + )), + ], + onCancel: close, + ); + }); +} + +void enter2FaDialog( + SessionID sessionId, OverlayDialogManager dialogManager) async { + final RxString code = "".obs; + dialogManager.dismissAll(); + dialogManager.show((setState, close, context) { + cancel() { + close(); + closeConnection(); + } + + submit() { + if (code.value.trim().length != 6) { + return; + } + gFFI.send2FA(sessionId, code.value.trim()); + close(); + dialogManager.showLoading(translate('Logging in...'), + onCancel: closeConnection); + } + + return CustomAlertDialog( + title: Text(translate("enter-2fa-title")), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + hintText: translate("Verification code")), + onChanged: (value) { + code.value = value; + }, + autofocus: true)) + ]), + ], + ), + onSubmit: submit, + onCancel: cancel, + actions: [ + dialogButton( + 'Cancel', + onPressed: cancel, + isOutline: true, + ), + Obx(() => dialogButton( + 'OK', + onPressed: code.value.trim().length == 6 ? submit : null, + )), + ]); + }); +} diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 80b474fa8..7e82dd359 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -10,7 +10,6 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; -import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; @@ -567,6 +566,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { child: Column(children: [ permissions(context), password(context), + _Card(title: '2FA', children: [tfa()]), _Card(title: 'ID', children: [changeId()]), more(context), ]), @@ -575,6 +575,45 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { )).marginOnly(bottom: _kListViewBottomMargin)); } + Widget tfa() { + bool enabled = !locked; + // Simple temp wrapper for PR check + tmpWrapper() { + RxBool has2fa = bind.mainHasValid2FaSync().obs; + update() async { + has2fa.value = bind.mainHasValid2FaSync(); + } + + onChanged(bool? checked) async { + change2fa(callback: update); + } + + return GestureDetector( + child: InkWell( + child: Obx(() => Row( + children: [ + Checkbox( + value: has2fa.value, + onChanged: enabled ? onChanged : null) + .marginOnly(right: 5), + Expanded( + child: Text( + translate('enable-2fa-title'), + style: + TextStyle(color: _disabledTextColor(context, enabled)), + )) + ], + )), + ), + onTap: () { + onChanged(!has2fa.value); + }, + ).marginOnly(left: _kCheckBoxLeftMargin); + } + + return tmpWrapper(); + } + Widget changeId() { return ChangeNotifierProvider.value( value: gFFI.serverModel, diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 644ebeef0..d0dbc325c 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -220,6 +220,16 @@ class _SettingsState extends State with WidgetsBindingObserver { Provider.of(context); final List enhancementsTiles = []; final List shareScreenTiles = [ + SettingsTile.switchTile( + title: Text(translate('enable-2fa-title')), + initialValue: bind.mainHasValid2FaSync(), + onToggle: (_) async { + update() async { + setState(() {}); + } + change2fa(callback: update); + }, + ), SettingsTile.switchTile( title: Text(translate('Deny LAN discovery')), initialValue: _denyLANDiscovery, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 73b56e077..5817e187a 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -498,6 +498,8 @@ class FfiModel with ChangeNotifier { final link = evt['link']; if (type == 're-input-password') { wrongPasswordDialog(sessionId, dialogManager, type, title, text); + } else if (type == 'input-2fa') { + enter2FaDialog(sessionId, dialogManager); } else if (type == 'input-password') { enterPasswordDialog(sessionId, dialogManager); } else if (type == 'session-login' || type == 'session-re-login') { @@ -2303,6 +2305,10 @@ class FFI { remember: remember); } + void send2FA(SessionID sessionId, String code) { + bind.sessionSend2Fa(sessionId: sessionId, code: code); + } + /// Close the remote session. Future close({bool closeSession = true}) async { closed = true; diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index f266daba4..dfdb90c58 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -1077,6 +1077,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" qr_code_scanner: dependency: "direct main" description: @@ -1085,6 +1093,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" quiver: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index db89d7a83..2ab290c6a 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -105,6 +105,7 @@ dependencies: ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9 pull_down_button: ^0.9.3 device_info_plus: ^9.1.0 + qr_flutter: ^4.1.0 dev_dependencies: icons_launcher: ^2.0.4 diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 6eacb12c9..22750c1ec 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -82,6 +82,10 @@ message LoginRequest { OSLogin os_login = 12; } +message Auth2FA { + string code = 1; +} + message ChatMessage { string text = 1; } message Features { @@ -771,5 +775,6 @@ message Message { VoiceCallResponse voice_call_response = 24; PeerInfo peer_info = 25; PointerDeviceEvent pointer_device_event = 26; + Auth2FA auth_2fa = 27; } } diff --git a/src/auth_2fa.rs b/src/auth_2fa.rs new file mode 100644 index 000000000..dc3095b87 --- /dev/null +++ b/src/auth_2fa.rs @@ -0,0 +1,105 @@ +use hbb_common::{ + bail, + config::Config, + get_time, + password_security::{decrypt_vec_or_original, encrypt_vec_or_original}, + ResultType, +}; +use serde_derive::{Deserialize, Serialize}; +use std::sync::Mutex; +use totp_rs::{Algorithm, Secret, TOTP}; + +lazy_static::lazy_static! { + static ref CURRENT_2FA: Mutex> = Mutex::new(None); +} + +const ISSUER: &str = "RustDesk"; +const TAG_LOGIN: &str = "Connection"; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TOTPInfo { + pub name: String, + pub secret: Vec, + pub digits: usize, + pub created_at: i64, +} + +impl TOTPInfo { + fn new_totp(&self) -> ResultType { + let totp = TOTP::new( + Algorithm::SHA1, + self.digits, + 1, + 30, + self.secret.clone(), + Some(format!("{} {}", ISSUER, TAG_LOGIN)), + self.name.clone(), + )?; + Ok(totp) + } + + fn gen_totp_info(name: String, digits: usize) -> ResultType { + let secret = Secret::generate_secret(); + let totp = TOTPInfo { + secret: secret.to_bytes()?, + name, + digits, + created_at: get_time(), + ..Default::default() + }; + Ok(totp) + } + + pub fn into_string(&self) -> ResultType { + let secret = encrypt_vec_or_original(self.secret.as_slice(), "00", 1024); + let totp_info = TOTPInfo { + secret, + ..self.clone() + }; + let s = serde_json::to_string(&totp_info)?; + Ok(s) + } + + pub fn from_str(data: &str) -> ResultType { + let mut totp_info = serde_json::from_str::(data)?; + let (secret, success, _) = decrypt_vec_or_original(&totp_info.secret, "00"); + if success { + totp_info.secret = secret; + return Ok(totp_info.new_totp()?); + } else { + bail!("decrypt_vec_or_original 2fa secret failed") + } + } +} + +pub fn generate2fa() -> String { + if let Ok(info) = TOTPInfo::gen_totp_info(crate::ipc::get_id(), 6) { + if let Ok(totp) = info.new_totp() { + let code = totp.get_url(); + *CURRENT_2FA.lock().unwrap() = Some((info, totp)); + return code; + } + } + "".to_owned() +} + +pub fn verify2fa(code: String) -> bool { + if let Some((info, totp)) = CURRENT_2FA.lock().unwrap().as_ref() { + if let Ok(cur) = totp.generate_current() { + let res = code == cur; + if res { + if let Ok(v) = info.into_string() { + crate::ipc::set_option("2fa", &v); + return res; + } + } + } + } + false +} + +pub fn get_2fa(raw: Option) -> Option { + TOTPInfo::from_str(&raw.unwrap_or(Config::get_option("2fa"))) + .map(|x| Some(x)) + .unwrap_or_default() +} diff --git a/src/client.rs b/src/client.rs index d35f8da1d..e8167b6c5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -92,6 +92,8 @@ pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG: &str = "Desktop session not ready, password wrong"; pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password"; pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password"; +pub const LOGIN_MSG_2FA_WRONG: &str = "Wrong 2FA Code"; +pub const REQUIRE_2FA: &'static str = "2FA Required"; pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access"; pub const LOGIN_MSG_OFFLINE: &str = "Offline"; pub const LOGIN_SCREEN_WAYLAND: &str = "Wayland login screen is not supported"; @@ -2561,6 +2563,10 @@ pub fn handle_login_error( lc.write().unwrap().password = Default::default(); interface.msgbox("re-input-password", err, "Do you want to enter again?", ""); true + } else if err == LOGIN_MSG_2FA_WRONG || err == REQUIRE_2FA { + lc.write().unwrap().password = Default::default(); + interface.msgbox("input-2fa", err, "", ""); + true } else if LOGIN_ERROR_MAP.contains_key(err) { if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) { interface.msgbox( diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d4a49003e..828d76f14 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -180,6 +180,15 @@ pub fn session_login( } } +pub fn session_send2fa( + session_id: SessionID, + code: String, +) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.send2fa(code); + } +} + 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); @@ -2016,6 +2025,23 @@ pub fn main_supported_input_source() -> SyncReturn { } } +pub fn main_generate2fa() -> String { + crate::auth_2fa::generate2fa() +} + +pub fn main_verify2fa(code: String) -> bool { + let res = crate::auth_2fa::verify2fa(code); + if res { + refresh_options(); + } + res +} + +pub fn main_has_valid_2fa_sync() -> SyncReturn { + let raw = get_option("2fa"); + SyncReturn(crate::auth_2fa::get_2fa(Some(raw)).is_some()) +} + #[cfg(target_os = "android")] pub mod server_side { use hbb_common::{config, log}; diff --git a/src/lang/ar.rs b/src/lang/ar.rs index caa49c143..50de78042 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 2a37c36c6..ed41ccb95 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 9fe91bddd..d63d79c74 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "双重认证代码"), ("2fa_tip", "请输入授权 app 中的双重认证代码"), ("More", "更多"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 5b4a523fe..1e44600c7 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "2FA kód"), ("2fa_tip", "Zadejte svůj kód 2FA do ověřovací aplikace."), ("More", "Více"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 0175bba44..60d07b397 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 2365927eb..0ff33bad3 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "2FA-Code"), ("2fa_tip", "Bitte geben Sie Ihren 2FA-Code in der Authentifizierungs-App ein."), ("More", "Mehr"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 184cdd115..f25e75d27 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "κωδικός 2FA"), ("2fa_tip", "Παρακαλώ εισάγεται τον κωδικό 2FA στην εφαρμογή πιστοποίησης"), ("More", "Περισσότερα"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index ddeb9567b..cc382e7f7 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -212,5 +212,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("swap-left-right-mouse", "Swap left-right mouse button"), ("2FA code", "2FA code"), ("2fa_tip", "Please enter your 2FA code in the authentication app."), + ("enable-2fa-title", "Enable two-factor authentication"), + ("enable-2fa-desc", "Please set up your authenticator now. You can use an authenticator app such as Authy, Microsoft or Google Authenticator on your phone or desktop.\n\nScan the QR code with your app and enter the code that your app shows to enable two-factor authentication."), + ("wrong-2fa-code", "Can't verify the code. Check that code and local time settings are correct"), + ("enter-2fa-title", "Two-factor authentication"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 7c4b75397..87eef532c 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 84b39095f..b602b689d 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 243992ebe..c3da54797 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 0441b389f..a6b083f4c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "کد ورود 2 مرحله ای"), ("2fa_tip", "لطفا کد ورود 2 مرحله ای خود را در برنامه احراز هویت وارد کنید"), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 1cae9241b..4defde37d 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index b18f740db..71490f6bf 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 0becea9cf..f7493b50c 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 059f8670b..793dd7849 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "Codice 2FA"), ("2fa_tip", "Inserisci il codice 2FA nell'app di autenticazione."), ("More", "Altro"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 1f23e7167..76f4a35b4 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 43f139b29..e065e8d76 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index ecbe399eb..e021f9799 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 240d85b72..8fcb4d49e 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index b90ab7ef5..fca516707 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "2FA kods"), ("2fa_tip", "Lūdzu, ievadiet savu 2FA kodu autentifikācijas lietotnē."), ("More", "Vairāk"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 580930b05..99a0ba9c7 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index b71b3775b..8ec4e48a0 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "2FA-code"), ("2fa_tip", "Geef je 2FA-code op in de verificatie-app."), ("More", "Meer"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index ff60850c5..a5d53208f 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "Kod 2FA"), ("2fa_tip", "Proszę wprowadzić swój kod 2FA w aplikacji do autoryzacji."), ("More", "Więcej"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index e9088214b..3f8818fbb 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 4e218cce5..12220b7ac 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 93c8f0010..2bca2b7b6 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 832be0128..cae965d8e 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "Код 2FA"), ("2fa_tip", "Введите код двухфакторной аутентификации из приложения для аутентификации."), ("More", "Ещё"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 4590ff66d..604c082df 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "2FA kód"), ("2fa_tip", "Zadajte svoj kód 2FA do aplikácie na overovanie."), ("More", "Viac"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 3faa062af..7e6716ce1 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 4cb980874..cf539bc08 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index e4ddd2c3b..33d12e8a5 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index a7f0f0fe8..e7577d85b 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 04b3a70f8..b76845b01 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 1dac813f2..2d3e9d54d 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index e9e7d8985..32e6a6eb8 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 3f77f70ce..6e50bfd36 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index a5740edd5..0dd3a9e50 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", "Код двофакторної автентифікації"), ("2fa_tip", "Будь ласка, введіть код двофакторної автентифікації в застосунку для автентифікації."), ("More", "Більше"), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 1701105dc..55e72c570 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code", ""), ("2fa_tip", ""), ("More", ""), + ("enable-2fa-title", ""), + ("enable-2fa-desc", ""), + ("wrong-2fa-code", ""), + ("enter-2fa-title", ""), ].iter().cloned().collect(); } diff --git a/src/lib.rs b/src/lib.rs index 2498045a7..4dbf498e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ mod lang; mod license; #[cfg(not(any(target_os = "android", target_os = "ios")))] mod port_forward; +mod auth_2fa; #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/server/connection.rs b/src/server/connection.rs index f87df116e..1f0685d9b 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -67,7 +67,7 @@ use std::collections::HashSet; pub type Sender = mpsc::UnboundedSender<(Instant, Arc)>; lazy_static::lazy_static! { - static ref LOGIN_FAILURES: Arc::>> = Default::default(); + static ref LOGIN_FAILURES: [Arc::>>; 2] = Default::default(); static ref SESSIONS: Arc::>> = Default::default(); static ref ALIVE_CONNS: Arc::>> = Default::default(); static ref AUTHED_CONNS: Arc::>> = Default::default(); @@ -150,6 +150,7 @@ struct Session { session_id: u64, last_recv_time: Arc>, random_password: String, + tfa: bool, } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -181,6 +182,7 @@ pub struct Connection { port_forward_address: String, tx_to_cm: mpsc::UnboundedSender, authorized: bool, + require_2fa: Option, keyboard: bool, clipboard: bool, audio: bool, @@ -317,6 +319,7 @@ impl Connection { tx: Some(tx), tx_video: Some(tx_video), }, + require_2fa: crate::auth_2fa::get_2fa(None), display_idx: *display_service::PRIMARY_DISPLAY_IDX, stream, server, @@ -434,6 +437,7 @@ impl Connection { Some(data) = rx_from_cm.recv() => { match data { ipc::Data::Authorize => { + conn.require_2fa.take(); conn.send_logon_response().await; if conn.port_forward_socket.is_some() { break; @@ -1029,6 +1033,10 @@ impl Connection { if self.authorized { return; } + if self.require_2fa.is_some() && !self.is_recent_session(true) { + self.send_login_error(crate::client::REQUIRE_2FA).await; + return; + } let (conn_type, auth_conn_type) = if self.file_transfer.is_some() { (1, AuthConnType::FileTransfer) } else if self.port_forward_socket.is_some() { @@ -1402,6 +1410,7 @@ impl Connection { session_id: self.lr.session_id, last_recv_time: self.last_recv_time.clone(), random_password: password, + tfa: false, }, ); return true; @@ -1415,7 +1424,7 @@ impl Connection { false } - fn is_recent_session(&mut self) -> bool { + fn is_recent_session(&mut self, tfa: bool) -> bool { SESSIONS .lock() .unwrap() @@ -1426,21 +1435,18 @@ impl Connection { .get(&self.lr.my_id) .map(|s| s.to_owned()); // last_recv_time is a mutex variable shared with connection, can be updated lively. - if let Some(session) = session { + if let Some(mut session) = session { if session.name == self.lr.my_name && session.session_id == self.lr.session_id && !self.lr.password.is_empty() - && self.validate_one_password(session.random_password.clone()) + && (tfa && session.tfa + || !tfa && self.validate_one_password(session.random_password.clone())) { - SESSIONS.lock().unwrap().insert( - self.lr.my_id.clone(), - Session { - name: self.lr.my_name.clone(), - session_id: self.lr.session_id, - last_recv_time: self.last_recv_time.clone(), - random_password: session.random_password, - }, - ); + session.last_recv_time = self.last_recv_time.clone(); + SESSIONS + .lock() + .unwrap() + .insert(self.lr.my_id.clone(), session); return true; } } @@ -1614,7 +1620,7 @@ impl Connection { { self.send_login_error("Connection not allowed").await; return false; - } else if self.is_recent_session() { + } else if self.is_recent_session(false) { if err_msg.is_empty() { #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] @@ -1637,47 +1643,12 @@ impl Connection { .await; } } else { - let mut failure = LOGIN_FAILURES - .lock() - .unwrap() - .get(&self.ip) - .map(|x| x.clone()) - .unwrap_or((0, 0, 0)); - let time = (get_time() / 60_000) as i32; - if failure.2 > 30 { - self.send_login_error("Too many wrong password attempts") - .await; - Self::post_alarm_audit( - AlarmAuditType::ExceedThirtyAttempts, - json!({ - "ip":self.ip, - "id":lr.my_id.clone(), - "name": lr.my_name.clone(), - }), - ); - } else if time == failure.0 && failure.1 > 6 { - self.send_login_error("Please try 1 minute later").await; - Self::post_alarm_audit( - AlarmAuditType::SixAttemptsWithinOneMinute, - json!({ - "ip":self.ip, - "id":lr.my_id.clone(), - "name": lr.my_name.clone(), - }), - ); - } else if !self.validate_password() { - if failure.0 == time { - failure.1 += 1; - failure.2 += 1; - } else { - failure.0 = time; - failure.1 = 1; - failure.2 += 1; - } - LOGIN_FAILURES - .lock() - .unwrap() - .insert(self.ip.clone(), failure); + let (failure, res) = self.check_failure(0).await; + if !res { + return true; + } + if !self.validate_password() { + self.update_failure(failure, false, 0); if err_msg.is_empty() { self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG) .await; @@ -1689,9 +1660,7 @@ impl Connection { .await; } } else { - if failure.0 != 0 { - LOGIN_FAILURES.lock().unwrap().remove(&self.ip); - } + self.update_failure(failure, true, 0); if err_msg.is_empty() { #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] @@ -1706,6 +1675,47 @@ impl Connection { } } } + } else if let Some(message::Union::Auth2fa(tfa)) = msg.union { + let (failure, res) = self.check_failure(1).await; + if !res { + return true; + } + if let Some(totp) = self.require_2fa.as_ref() { + if let Ok(code) = totp.generate_current() { + if tfa.code == code { + self.update_failure(failure, true, 1); + self.require_2fa.take(); + self.send_logon_response().await; + let session = SESSIONS + .lock() + .unwrap() + .get(&self.lr.my_id) + .map(|s| s.to_owned()); + if let Some(mut session) = session { + session.tfa = true; + SESSIONS + .lock() + .unwrap() + .insert(self.lr.my_id.clone(), session); + } else { + SESSIONS.lock().unwrap().insert( + self.lr.my_id.clone(), + Session { + name: self.lr.my_name.clone(), + session_id: self.lr.session_id, + last_recv_time: self.last_recv_time.clone(), + random_password: "".to_owned(), + tfa: true, + }, + ); + } + } else { + self.update_failure(failure, false, 1); + self.send_login_error(crate::client::LOGIN_MSG_2FA_WRONG) + .await; + } + } + } } else if let Some(message::Union::TestDelay(t)) = msg.union { if t.from_client { let mut msg_out = Message::new(); @@ -2245,6 +2255,63 @@ impl Connection { true } + fn update_failure(&self, (mut failure, time): ((i32, i32, i32), i32), remove: bool, i: usize) { + if remove { + if failure.0 != 0 { + LOGIN_FAILURES[i].lock().unwrap().remove(&self.ip); + } + return; + } + if failure.0 == time { + failure.1 += 1; + failure.2 += 1; + } else { + failure.0 = time; + failure.1 = 1; + failure.2 += 1; + } + LOGIN_FAILURES[i] + .lock() + .unwrap() + .insert(self.ip.clone(), failure); + } + + async fn check_failure(&mut self, i: usize) -> (((i32, i32, i32), i32), bool) { + let failure = LOGIN_FAILURES[i] + .lock() + .unwrap() + .get(&self.ip) + .map(|x| x.clone()) + .unwrap_or((0, 0, 0)); + let time = (get_time() / 60_000) as i32; + let res = if failure.2 > 30 { + self.send_login_error("Too many wrong attempts").await; + Self::post_alarm_audit( + AlarmAuditType::ExceedThirtyAttempts, + json!({ + "ip": self.ip, + "id": self.lr.my_id.clone(), + "name": self.lr.my_name.clone(), + }), + ); + false + } else if time == failure.0 && failure.1 > 6 { + self.send_login_error("Please try 1 minute later").await; + Self::post_alarm_audit( + AlarmAuditType::SixAttemptsWithinOneMinute, + json!({ + "ip": self.ip, + "id": self.lr.my_id.clone(), + "name": self.lr.my_name.clone(), + }), + ); + false + } else { + true + }; + ((failure, time), res) + } + fn refresh_video_display(&self, display: Option) { video_service::refresh(); self.server.upgrade().map(|s| { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 618250f1b..5d76dfafb 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -141,6 +141,14 @@ pub fn get_license() -> String { Default::default() } +#[inline] +pub fn refresh_options() { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + *OPTIONS.lock().unwrap() = Config::get_options(); + } +} + #[inline] pub fn get_option>(key: T) -> String { #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 157567556..217830826 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1082,6 +1082,15 @@ impl Session { self.send(Data::Login((os_username, os_password, password, remember))); } + pub fn send2fa(&self, code: String) { + let mut msg_out = Message::new(); + msg_out.set_auth_2fa(Auth2FA { + code, + ..Default::default() + }); + self.send(Data::Message(msg_out)); + } + pub fn new_rdp(&self) { self.send(Data::NewRDP); }