2fa for unattended access
This commit is contained in:
parent
80857c22c9
commit
44e6b7dbb0
37
Cargo.lock
generated
37
Cargo.lock
generated
@ -518,6 +518,12 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base32"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.5"
|
version = "0.21.5"
|
||||||
@ -1135,6 +1141,12 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -5427,6 +5439,7 @@ dependencies = [
|
|||||||
"system_shutdown",
|
"system_shutdown",
|
||||||
"tao",
|
"tao",
|
||||||
"tauri-winrt-notification",
|
"tauri-winrt-notification",
|
||||||
|
"totp-rs",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"url",
|
"url",
|
||||||
"users 0.11.0",
|
"users 0.11.0",
|
||||||
@ -6503,6 +6516,22 @@ dependencies = [
|
|||||||
"winnow",
|
"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]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -6701,6 +6730,12 @@ dependencies = [
|
|||||||
"serde 1.0.190",
|
"serde 1.0.190",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "users"
|
name = "users"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -7726,7 +7761,7 @@ dependencies = [
|
|||||||
"aes",
|
"aes",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"constant_time_eq",
|
"constant_time_eq 0.1.5",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -84,6 +84,7 @@ libloading = "0.8"
|
|||||||
fon = "0.6"
|
fon = "0.6"
|
||||||
zip = "0.6"
|
zip = "0.6"
|
||||||
shutdown_hooks = "0.1"
|
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]
|
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
||||||
cpal = "0.15"
|
cpal = "0.15"
|
||||||
|
@ -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/common/widgets/setting_widgets.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/model.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,
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -10,7 +10,6 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
|||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.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/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/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
import 'package:flutter_hbb/plugin/manager.dart';
|
import 'package:flutter_hbb/plugin/manager.dart';
|
||||||
@ -567,6 +566,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
permissions(context),
|
permissions(context),
|
||||||
password(context),
|
password(context),
|
||||||
|
_Card(title: '2FA', children: [tfa()]),
|
||||||
_Card(title: 'ID', children: [changeId()]),
|
_Card(title: 'ID', children: [changeId()]),
|
||||||
more(context),
|
more(context),
|
||||||
]),
|
]),
|
||||||
@ -575,6 +575,45 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
)).marginOnly(bottom: _kListViewBottomMargin));
|
)).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() {
|
Widget changeId() {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: gFFI.serverModel,
|
value: gFFI.serverModel,
|
||||||
|
@ -220,6 +220,16 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
Provider.of<FfiModel>(context);
|
Provider.of<FfiModel>(context);
|
||||||
final List<AbstractSettingsTile> enhancementsTiles = [];
|
final List<AbstractSettingsTile> enhancementsTiles = [];
|
||||||
final List<AbstractSettingsTile> shareScreenTiles = [
|
final List<AbstractSettingsTile> shareScreenTiles = [
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
title: Text(translate('enable-2fa-title')),
|
||||||
|
initialValue: bind.mainHasValid2FaSync(),
|
||||||
|
onToggle: (_) async {
|
||||||
|
update() async {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
change2fa(callback: update);
|
||||||
|
},
|
||||||
|
),
|
||||||
SettingsTile.switchTile(
|
SettingsTile.switchTile(
|
||||||
title: Text(translate('Deny LAN discovery')),
|
title: Text(translate('Deny LAN discovery')),
|
||||||
initialValue: _denyLANDiscovery,
|
initialValue: _denyLANDiscovery,
|
||||||
|
@ -498,6 +498,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
final link = evt['link'];
|
final link = evt['link'];
|
||||||
if (type == 're-input-password') {
|
if (type == 're-input-password') {
|
||||||
wrongPasswordDialog(sessionId, dialogManager, type, title, text);
|
wrongPasswordDialog(sessionId, dialogManager, type, title, text);
|
||||||
|
} else if (type == 'input-2fa') {
|
||||||
|
enter2FaDialog(sessionId, dialogManager);
|
||||||
} else if (type == 'input-password') {
|
} else if (type == 'input-password') {
|
||||||
enterPasswordDialog(sessionId, dialogManager);
|
enterPasswordDialog(sessionId, dialogManager);
|
||||||
} else if (type == 'session-login' || type == 'session-re-login') {
|
} else if (type == 'session-login' || type == 'session-re-login') {
|
||||||
@ -2303,6 +2305,10 @@ class FFI {
|
|||||||
remember: remember);
|
remember: remember);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send2FA(SessionID sessionId, String code) {
|
||||||
|
bind.sessionSend2Fa(sessionId: sessionId, code: code);
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the remote session.
|
/// Close the remote session.
|
||||||
Future<void> close({bool closeSession = true}) async {
|
Future<void> close({bool closeSession = true}) async {
|
||||||
closed = true;
|
closed = true;
|
||||||
|
@ -1077,6 +1077,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
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:
|
qr_code_scanner:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1085,6 +1093,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
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:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -105,6 +105,7 @@ dependencies:
|
|||||||
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
|
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
|
||||||
pull_down_button: ^0.9.3
|
pull_down_button: ^0.9.3
|
||||||
device_info_plus: ^9.1.0
|
device_info_plus: ^9.1.0
|
||||||
|
qr_flutter: ^4.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
icons_launcher: ^2.0.4
|
icons_launcher: ^2.0.4
|
||||||
|
@ -82,6 +82,10 @@ message LoginRequest {
|
|||||||
OSLogin os_login = 12;
|
OSLogin os_login = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Auth2FA {
|
||||||
|
string code = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ChatMessage { string text = 1; }
|
message ChatMessage { string text = 1; }
|
||||||
|
|
||||||
message Features {
|
message Features {
|
||||||
@ -771,5 +775,6 @@ message Message {
|
|||||||
VoiceCallResponse voice_call_response = 24;
|
VoiceCallResponse voice_call_response = 24;
|
||||||
PeerInfo peer_info = 25;
|
PeerInfo peer_info = 25;
|
||||||
PointerDeviceEvent pointer_device_event = 26;
|
PointerDeviceEvent pointer_device_event = 26;
|
||||||
|
Auth2FA auth_2fa = 27;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
105
src/auth_2fa.rs
Normal file
105
src/auth_2fa.rs
Normal file
@ -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<Option<(TOTPInfo, TOTP)>> = 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<u8>,
|
||||||
|
pub digits: usize,
|
||||||
|
pub created_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TOTPInfo {
|
||||||
|
fn new_totp(&self) -> ResultType<TOTP> {
|
||||||
|
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<TOTPInfo> {
|
||||||
|
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<String> {
|
||||||
|
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<TOTP> {
|
||||||
|
let mut totp_info = serde_json::from_str::<TOTPInfo>(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<String>) -> Option<TOTP> {
|
||||||
|
TOTPInfo::from_str(&raw.unwrap_or(Config::get_option("2fa")))
|
||||||
|
.map(|x| Some(x))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
@ -92,6 +92,8 @@ pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG: &str =
|
|||||||
"Desktop session not ready, password wrong";
|
"Desktop session not ready, password wrong";
|
||||||
pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
|
pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
|
||||||
pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong 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_NO_PASSWORD_ACCESS: &str = "No Password Access";
|
||||||
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
|
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
|
||||||
pub const LOGIN_SCREEN_WAYLAND: &str = "Wayland login screen is not supported";
|
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();
|
lc.write().unwrap().password = Default::default();
|
||||||
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 {
|
||||||
|
lc.write().unwrap().password = Default::default();
|
||||||
|
interface.msgbox("input-2fa", err, "", "");
|
||||||
|
true
|
||||||
} else if LOGIN_ERROR_MAP.contains_key(err) {
|
} else if LOGIN_ERROR_MAP.contains_key(err) {
|
||||||
if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) {
|
if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) {
|
||||||
interface.msgbox(
|
interface.msgbox(
|
||||||
|
@ -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) {
|
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);
|
||||||
@ -2016,6 +2025,23 @@ pub fn main_supported_input_source() -> SyncReturn<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<bool> {
|
||||||
|
let raw = get_option("2fa");
|
||||||
|
SyncReturn(crate::auth_2fa::get_2fa(Some(raw)).is_some())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::{config, log};
|
use hbb_common::{config, log};
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "双重认证代码"),
|
("2FA code", "双重认证代码"),
|
||||||
("2fa_tip", "请输入授权 app 中的双重认证代码"),
|
("2fa_tip", "请输入授权 app 中的双重认证代码"),
|
||||||
("More", "更多"),
|
("More", "更多"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "2FA kód"),
|
("2FA code", "2FA kód"),
|
||||||
("2fa_tip", "Zadejte svůj kód 2FA do ověřovací aplikace."),
|
("2fa_tip", "Zadejte svůj kód 2FA do ověřovací aplikace."),
|
||||||
("More", "Více"),
|
("More", "Více"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "2FA-Code"),
|
("2FA code", "2FA-Code"),
|
||||||
("2fa_tip", "Bitte geben Sie Ihren 2FA-Code in der Authentifizierungs-App ein."),
|
("2fa_tip", "Bitte geben Sie Ihren 2FA-Code in der Authentifizierungs-App ein."),
|
||||||
("More", "Mehr"),
|
("More", "Mehr"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "κωδικός 2FA"),
|
("2FA code", "κωδικός 2FA"),
|
||||||
("2fa_tip", "Παρακαλώ εισάγεται τον κωδικό 2FA στην εφαρμογή πιστοποίησης"),
|
("2fa_tip", "Παρακαλώ εισάγεται τον κωδικό 2FA στην εφαρμογή πιστοποίησης"),
|
||||||
("More", "Περισσότερα"),
|
("More", "Περισσότερα"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -212,5 +212,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("swap-left-right-mouse", "Swap left-right mouse button"),
|
("swap-left-right-mouse", "Swap left-right mouse button"),
|
||||||
("2FA code", "2FA code"),
|
("2FA code", "2FA code"),
|
||||||
("2fa_tip", "Please enter your 2FA code in the authentication app."),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "کد ورود 2 مرحله ای"),
|
("2FA code", "کد ورود 2 مرحله ای"),
|
||||||
("2fa_tip", "لطفا کد ورود 2 مرحله ای خود را در برنامه احراز هویت وارد کنید"),
|
("2fa_tip", "لطفا کد ورود 2 مرحله ای خود را در برنامه احراز هویت وارد کنید"),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "Codice 2FA"),
|
("2FA code", "Codice 2FA"),
|
||||||
("2fa_tip", "Inserisci il codice 2FA nell'app di autenticazione."),
|
("2fa_tip", "Inserisci il codice 2FA nell'app di autenticazione."),
|
||||||
("More", "Altro"),
|
("More", "Altro"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "2FA kods"),
|
("2FA code", "2FA kods"),
|
||||||
("2fa_tip", "Lūdzu, ievadiet savu 2FA kodu autentifikācijas lietotnē."),
|
("2fa_tip", "Lūdzu, ievadiet savu 2FA kodu autentifikācijas lietotnē."),
|
||||||
("More", "Vairāk"),
|
("More", "Vairāk"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "2FA-code"),
|
("2FA code", "2FA-code"),
|
||||||
("2fa_tip", "Geef je 2FA-code op in de verificatie-app."),
|
("2fa_tip", "Geef je 2FA-code op in de verificatie-app."),
|
||||||
("More", "Meer"),
|
("More", "Meer"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "Kod 2FA"),
|
("2FA code", "Kod 2FA"),
|
||||||
("2fa_tip", "Proszę wprowadzić swój kod 2FA w aplikacji do autoryzacji."),
|
("2fa_tip", "Proszę wprowadzić swój kod 2FA w aplikacji do autoryzacji."),
|
||||||
("More", "Więcej"),
|
("More", "Więcej"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "Код 2FA"),
|
("2FA code", "Код 2FA"),
|
||||||
("2fa_tip", "Введите код двухфакторной аутентификации из приложения для аутентификации."),
|
("2fa_tip", "Введите код двухфакторной аутентификации из приложения для аутентификации."),
|
||||||
("More", "Ещё"),
|
("More", "Ещё"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "2FA kód"),
|
("2FA code", "2FA kód"),
|
||||||
("2fa_tip", "Zadajte svoj kód 2FA do aplikácie na overovanie."),
|
("2fa_tip", "Zadajte svoj kód 2FA do aplikácie na overovanie."),
|
||||||
("More", "Viac"),
|
("More", "Viac"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", "Код двофакторної автентифікації"),
|
("2FA code", "Код двофакторної автентифікації"),
|
||||||
("2fa_tip", "Будь ласка, введіть код двофакторної автентифікації в застосунку для автентифікації."),
|
("2fa_tip", "Будь ласка, введіть код двофакторної автентифікації в застосунку для автентифікації."),
|
||||||
("More", "Більше"),
|
("More", "Більше"),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -581,5 +581,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("2FA code", ""),
|
("2FA code", ""),
|
||||||
("2fa_tip", ""),
|
("2fa_tip", ""),
|
||||||
("More", ""),
|
("More", ""),
|
||||||
|
("enable-2fa-title", ""),
|
||||||
|
("enable-2fa-desc", ""),
|
||||||
|
("wrong-2fa-code", ""),
|
||||||
|
("enter-2fa-title", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ mod lang;
|
|||||||
mod license;
|
mod license;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod port_forward;
|
mod port_forward;
|
||||||
|
mod auth_2fa;
|
||||||
|
|
||||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
@ -67,7 +67,7 @@ use std::collections::HashSet;
|
|||||||
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
|
static ref LOGIN_FAILURES: [Arc::<Mutex<HashMap<String, (i32, i32, i32)>>>; 2] = Default::default();
|
||||||
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
||||||
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
||||||
static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType)>>> = Default::default();
|
static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType)>>> = Default::default();
|
||||||
@ -150,6 +150,7 @@ struct Session {
|
|||||||
session_id: u64,
|
session_id: u64,
|
||||||
last_recv_time: Arc<Mutex<Instant>>,
|
last_recv_time: Arc<Mutex<Instant>>,
|
||||||
random_password: String,
|
random_password: String,
|
||||||
|
tfa: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
@ -181,6 +182,7 @@ pub struct Connection {
|
|||||||
port_forward_address: String,
|
port_forward_address: String,
|
||||||
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||||
authorized: bool,
|
authorized: bool,
|
||||||
|
require_2fa: Option<totp_rs::TOTP>,
|
||||||
keyboard: bool,
|
keyboard: bool,
|
||||||
clipboard: bool,
|
clipboard: bool,
|
||||||
audio: bool,
|
audio: bool,
|
||||||
@ -317,6 +319,7 @@ impl Connection {
|
|||||||
tx: Some(tx),
|
tx: Some(tx),
|
||||||
tx_video: Some(tx_video),
|
tx_video: Some(tx_video),
|
||||||
},
|
},
|
||||||
|
require_2fa: crate::auth_2fa::get_2fa(None),
|
||||||
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
|
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
|
||||||
stream,
|
stream,
|
||||||
server,
|
server,
|
||||||
@ -434,6 +437,7 @@ impl Connection {
|
|||||||
Some(data) = rx_from_cm.recv() => {
|
Some(data) = rx_from_cm.recv() => {
|
||||||
match data {
|
match data {
|
||||||
ipc::Data::Authorize => {
|
ipc::Data::Authorize => {
|
||||||
|
conn.require_2fa.take();
|
||||||
conn.send_logon_response().await;
|
conn.send_logon_response().await;
|
||||||
if conn.port_forward_socket.is_some() {
|
if conn.port_forward_socket.is_some() {
|
||||||
break;
|
break;
|
||||||
@ -1029,6 +1033,10 @@ impl Connection {
|
|||||||
if self.authorized {
|
if self.authorized {
|
||||||
return;
|
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() {
|
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
||||||
(1, AuthConnType::FileTransfer)
|
(1, AuthConnType::FileTransfer)
|
||||||
} else if self.port_forward_socket.is_some() {
|
} else if self.port_forward_socket.is_some() {
|
||||||
@ -1402,6 +1410,7 @@ impl Connection {
|
|||||||
session_id: self.lr.session_id,
|
session_id: self.lr.session_id,
|
||||||
last_recv_time: self.last_recv_time.clone(),
|
last_recv_time: self.last_recv_time.clone(),
|
||||||
random_password: password,
|
random_password: password,
|
||||||
|
tfa: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
@ -1415,7 +1424,7 @@ impl Connection {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_recent_session(&mut self) -> bool {
|
fn is_recent_session(&mut self, tfa: bool) -> bool {
|
||||||
SESSIONS
|
SESSIONS
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -1426,21 +1435,18 @@ impl Connection {
|
|||||||
.get(&self.lr.my_id)
|
.get(&self.lr.my_id)
|
||||||
.map(|s| s.to_owned());
|
.map(|s| s.to_owned());
|
||||||
// last_recv_time is a mutex variable shared with connection, can be updated lively.
|
// 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
|
if session.name == self.lr.my_name
|
||||||
&& session.session_id == self.lr.session_id
|
&& session.session_id == self.lr.session_id
|
||||||
&& !self.lr.password.is_empty()
|
&& !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(
|
session.last_recv_time = self.last_recv_time.clone();
|
||||||
self.lr.my_id.clone(),
|
SESSIONS
|
||||||
Session {
|
.lock()
|
||||||
name: self.lr.my_name.clone(),
|
.unwrap()
|
||||||
session_id: self.lr.session_id,
|
.insert(self.lr.my_id.clone(), session);
|
||||||
last_recv_time: self.last_recv_time.clone(),
|
|
||||||
random_password: session.random_password,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1614,7 +1620,7 @@ impl Connection {
|
|||||||
{
|
{
|
||||||
self.send_login_error("Connection not allowed").await;
|
self.send_login_error("Connection not allowed").await;
|
||||||
return false;
|
return false;
|
||||||
} else if self.is_recent_session() {
|
} else if self.is_recent_session(false) {
|
||||||
if err_msg.is_empty() {
|
if err_msg.is_empty() {
|
||||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||||
@ -1637,47 +1643,12 @@ impl Connection {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut failure = LOGIN_FAILURES
|
let (failure, res) = self.check_failure(0).await;
|
||||||
.lock()
|
if !res {
|
||||||
.unwrap()
|
return true;
|
||||||
.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
|
if !self.validate_password() {
|
||||||
.lock()
|
self.update_failure(failure, false, 0);
|
||||||
.unwrap()
|
|
||||||
.insert(self.ip.clone(), failure);
|
|
||||||
if err_msg.is_empty() {
|
if err_msg.is_empty() {
|
||||||
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
||||||
.await;
|
.await;
|
||||||
@ -1689,9 +1660,7 @@ impl Connection {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if failure.0 != 0 {
|
self.update_failure(failure, true, 0);
|
||||||
LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
|
|
||||||
}
|
|
||||||
if err_msg.is_empty() {
|
if err_msg.is_empty() {
|
||||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
#[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 {
|
} else if let Some(message::Union::TestDelay(t)) = msg.union {
|
||||||
if t.from_client {
|
if t.from_client {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
@ -2245,6 +2255,63 @@ impl Connection {
|
|||||||
true
|
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<usize>) {
|
fn refresh_video_display(&self, display: Option<usize>) {
|
||||||
video_service::refresh();
|
video_service::refresh();
|
||||||
self.server.upgrade().map(|s| {
|
self.server.upgrade().map(|s| {
|
||||||
|
@ -141,6 +141,14 @@ pub fn get_license() -> String {
|
|||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_options() {
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
*OPTIONS.lock().unwrap() = Config::get_options();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_option<T: AsRef<str>>(key: T) -> String {
|
pub fn get_option<T: AsRef<str>>(key: T) -> String {
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
@ -1082,6 +1082,15 @@ 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) {
|
||||||
|
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) {
|
pub fn new_rdp(&self) {
|
||||||
self.send(Data::NewRDP);
|
self.send(Data::NewRDP);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user