mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-16 17:21:28 +03:00
refact(password): Store permanent password as hashed verifier (#14619)
* refact(password): Store permanent password as hashed verifier Signed-off-by: fufesou <linlong1266@gmail.com> * fix(password): remove unused code Signed-off-by: fufesou <linlong1266@gmail.com> * fix(password): mobile, password dialog, width 500 Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -2377,8 +2377,9 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
|||||||
final password = uri.path.substring("/".length);
|
final password = uri.path.substring("/".length);
|
||||||
if (password.isNotEmpty) {
|
if (password.isNotEmpty) {
|
||||||
Timer(Duration(seconds: 1), () async {
|
Timer(Duration(seconds: 1), () async {
|
||||||
await bind.mainSetPermanentPassword(password: password);
|
final ok =
|
||||||
showToast(translate('Successful'));
|
await bind.mainSetPermanentPasswordWithResult(password: password);
|
||||||
|
showToast(translate(ok ? 'Successful' : 'Failed'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -908,12 +908,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
||||||
final pw = await bind.mainGetPermanentPassword();
|
final p0 = TextEditingController(text: "");
|
||||||
final p0 = TextEditingController(text: pw);
|
final p1 = TextEditingController(text: "");
|
||||||
final p1 = TextEditingController(text: pw);
|
|
||||||
var errMsg0 = "";
|
var errMsg0 = "";
|
||||||
var errMsg1 = "";
|
var errMsg1 = "";
|
||||||
final RxString rxPass = pw.trim().obs;
|
final localPasswordSet =
|
||||||
|
(await bind.mainGetCommon(key: "local-permanent-password-set")) == "true";
|
||||||
|
final permanentPasswordSet =
|
||||||
|
(await bind.mainGetCommon(key: "permanent-password-set")) == "true";
|
||||||
|
final presetPassword = permanentPasswordSet && !localPasswordSet;
|
||||||
|
var canSubmit = false;
|
||||||
|
final RxString rxPass = "".obs;
|
||||||
final rules = [
|
final rules = [
|
||||||
DigitValidationRule(),
|
DigitValidationRule(),
|
||||||
UppercaseValidationRule(),
|
UppercaseValidationRule(),
|
||||||
@@ -922,9 +927,21 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
MinCharactersValidationRule(8),
|
MinCharactersValidationRule(8),
|
||||||
];
|
];
|
||||||
final maxLength = bind.mainMaxEncryptLen();
|
final maxLength = bind.mainMaxEncryptLen();
|
||||||
|
final statusTip = localPasswordSet
|
||||||
|
? translate('password-hidden-tip')
|
||||||
|
: (presetPassword ? translate('preset-password-in-use-tip') : '');
|
||||||
|
final showStatusTipOnMobile =
|
||||||
|
statusTip.isNotEmpty && !isDesktop && !isWebDesktop;
|
||||||
|
|
||||||
gFFI.dialogManager.show((setState, close, context) {
|
gFFI.dialogManager.show((setState, close, context) {
|
||||||
submit() {
|
updateCanSubmit() {
|
||||||
|
canSubmit = p0.text.trim().isNotEmpty || p1.text.trim().isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() async {
|
||||||
|
if (!canSubmit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
errMsg0 = "";
|
errMsg0 = "";
|
||||||
errMsg1 = "";
|
errMsg1 = "";
|
||||||
@@ -947,7 +964,13 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bind.mainSetPermanentPassword(password: pass);
|
final ok = await bind.mainSetPermanentPasswordWithResult(password: pass);
|
||||||
|
if (!ok) {
|
||||||
|
setState(() {
|
||||||
|
errMsg0 = '${translate('Prompt')}: ${translate("Failed")}';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (pass.isNotEmpty) {
|
if (pass.isNotEmpty) {
|
||||||
notEmptyCallback?.call();
|
notEmptyCallback?.call();
|
||||||
}
|
}
|
||||||
@@ -955,14 +978,20 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Text(translate("Set Password")),
|
title: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.key, color: MyTheme.accent),
|
||||||
|
Text(translate("Set Password")).paddingOnly(left: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minWidth: 500),
|
constraints: const BoxConstraints(minWidth: 500),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 8.0,
|
height: showStatusTipOnMobile ? 0.0 : 6.0,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -978,6 +1007,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
rxPass.value = value.trim();
|
rxPass.value = value.trim();
|
||||||
setState(() {
|
setState(() {
|
||||||
errMsg0 = '';
|
errMsg0 = '';
|
||||||
|
updateCanSubmit();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
@@ -989,9 +1019,9 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(child: PasswordStrengthIndicator(password: rxPass)),
|
Expanded(child: PasswordStrengthIndicator(password: rxPass)),
|
||||||
],
|
],
|
||||||
).marginSymmetric(vertical: 8),
|
).marginOnly(top: 2, bottom: showStatusTipOnMobile ? 2 : 8),
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 8.0,
|
height: showStatusTipOnMobile ? 0.0 : 8.0,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -1005,6 +1035,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
errMsg1 = '';
|
errMsg1 = '';
|
||||||
|
updateCanSubmit();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
@@ -1012,11 +1043,23 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
if (statusTip.isNotEmpty)
|
||||||
height: 8.0,
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info, color: Colors.amber, size: 18)
|
||||||
|
.marginOnly(right: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
statusTip,
|
||||||
|
style: const TextStyle(fontSize: 13, height: 1.1),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
).marginOnly(top: 6, bottom: 2),
|
||||||
|
SizedBox(
|
||||||
|
height: showStatusTipOnMobile ? 0.0 : 8.0,
|
||||||
),
|
),
|
||||||
Obx(() => Wrap(
|
Obx(() => Wrap(
|
||||||
runSpacing: 8,
|
runSpacing: showStatusTipOnMobile ? 2.0 : 8.0,
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: rules.map((e) {
|
children: rules.map((e) {
|
||||||
var checked = e.validate(rxPass.value.trim());
|
var checked = e.validate(rxPass.value.trim());
|
||||||
@@ -1036,11 +1079,67 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: (() {
|
||||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
final cancelButton = dialogButton(
|
||||||
dialogButton("OK", onPressed: submit),
|
"Cancel",
|
||||||
],
|
icon: Icon(Icons.close_rounded),
|
||||||
onSubmit: submit,
|
onPressed: close,
|
||||||
|
isOutline: true,
|
||||||
|
);
|
||||||
|
final removeButton = dialogButton(
|
||||||
|
"Remove",
|
||||||
|
icon: Icon(Icons.delete_outline_rounded),
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
errMsg0 = "";
|
||||||
|
errMsg1 = "";
|
||||||
|
});
|
||||||
|
final ok =
|
||||||
|
await bind.mainSetPermanentPasswordWithResult(password: "");
|
||||||
|
if (!ok) {
|
||||||
|
setState(() {
|
||||||
|
errMsg0 = '${translate('Prompt')}: ${translate("Failed")}';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStatePropertyAll(Colors.red)),
|
||||||
|
);
|
||||||
|
final okButton = dialogButton(
|
||||||
|
"OK",
|
||||||
|
icon: Icon(Icons.done_rounded),
|
||||||
|
onPressed: canSubmit ? submit : null,
|
||||||
|
);
|
||||||
|
if (!isDesktop && !isWebDesktop && localPasswordSet) {
|
||||||
|
return [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
cancelButton,
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
removeButton,
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
okButton,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
cancelButton,
|
||||||
|
if (localPasswordSet) removeButton,
|
||||||
|
okButton,
|
||||||
|
];
|
||||||
|
})(),
|
||||||
|
onSubmit: canSubmit ? submit : null,
|
||||||
onCancel: close,
|
onCancel: close,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1109,8 +1109,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
if (value ==
|
if (value ==
|
||||||
passwordValues[passwordKeys
|
passwordValues[passwordKeys
|
||||||
.indexOf(kUsePermanentPassword)] &&
|
.indexOf(kUsePermanentPassword)] &&
|
||||||
(await bind.mainGetPermanentPassword())
|
(await bind.mainGetCommon(
|
||||||
.isEmpty) {
|
key: "permanent-password-set")) !=
|
||||||
|
"true") {
|
||||||
if (isChangePermanentPasswordDisabled()) {
|
if (isChangePermanentPasswordDisabled()) {
|
||||||
await callback();
|
await callback();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ class _DropDownAction extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (value == kUsePermanentPassword &&
|
if (value == kUsePermanentPassword &&
|
||||||
(await bind.mainGetPermanentPassword()).isEmpty) {
|
(await bind.mainGetCommon(key: "permanent-password-set")) !=
|
||||||
|
"true") {
|
||||||
if (isChangePermanentPasswordDisabled()) {
|
if (isChangePermanentPasswordDisabled()) {
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,100 +12,6 @@ void _showSuccess() {
|
|||||||
showToast(translate("Successful"));
|
showToast(translate("Successful"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showError() {
|
|
||||||
showToast(translate("Error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
|
||||||
final pw = await bind.mainGetPermanentPassword();
|
|
||||||
final p0 = TextEditingController(text: pw);
|
|
||||||
final p1 = TextEditingController(text: pw);
|
|
||||||
var validateLength = false;
|
|
||||||
var validateSame = false;
|
|
||||||
dialogManager.show((setState, close, context) {
|
|
||||||
submit() async {
|
|
||||||
close();
|
|
||||||
dialogManager.showLoading(translate("Waiting"));
|
|
||||||
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
_showSuccess();
|
|
||||||
} else {
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
_showError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.password_rounded, color: MyTheme.accent),
|
|
||||||
Text(translate('Set your own password')).paddingOnly(left: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: Form(
|
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
TextFormField(
|
|
||||||
autofocus: true,
|
|
||||||
obscureText: true,
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('Password'),
|
|
||||||
),
|
|
||||||
controller: p0,
|
|
||||||
validator: (v) {
|
|
||||||
if (v == null) return null;
|
|
||||||
final val = v.trim().length > 5;
|
|
||||||
if (validateLength != val) {
|
|
||||||
// use delay to make setState success
|
|
||||||
Future.delayed(Duration(microseconds: 1),
|
|
||||||
() => setState(() => validateLength = val));
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
? null
|
|
||||||
: translate('Too short, at least 6 characters.');
|
|
||||||
},
|
|
||||||
).workaroundFreezeLinuxMint(),
|
|
||||||
TextFormField(
|
|
||||||
obscureText: true,
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('Confirmation'),
|
|
||||||
),
|
|
||||||
controller: p1,
|
|
||||||
validator: (v) {
|
|
||||||
if (v == null) return null;
|
|
||||||
final val = p0.text == v;
|
|
||||||
if (validateSame != val) {
|
|
||||||
Future.delayed(Duration(microseconds: 1),
|
|
||||||
() => setState(() => validateSame = val));
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
? null
|
|
||||||
: translate('The confirmation is not identical.');
|
|
||||||
},
|
|
||||||
).workaroundFreezeLinuxMint(),
|
|
||||||
])),
|
|
||||||
onCancel: close,
|
|
||||||
onSubmit: (validateLength && validateSame) ? submit : null,
|
|
||||||
actions: [
|
|
||||||
dialogButton(
|
|
||||||
'Cancel',
|
|
||||||
icon: Icon(Icons.close_rounded),
|
|
||||||
onPressed: close,
|
|
||||||
isOutline: true,
|
|
||||||
),
|
|
||||||
dialogButton(
|
|
||||||
'OK',
|
|
||||||
icon: Icon(Icons.done_rounded),
|
|
||||||
onPressed: (validateLength && validateSame) ? submit : null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTemporaryPasswordLengthDialog(
|
void setTemporaryPasswordLengthDialog(
|
||||||
OverlayDialogManager dialogManager) async {
|
OverlayDialogManager dialogManager) async {
|
||||||
List<String> lengths = ['6', '8', '10'];
|
List<String> lengths = ['6', '8', '10'];
|
||||||
|
|||||||
@@ -471,17 +471,6 @@ class ServerModel with ChangeNotifier {
|
|||||||
WakelockManager.disable(_wakelockKey);
|
WakelockManager.disable(_wakelockKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setPermanentPassword(String newPW) async {
|
|
||||||
await bind.mainSetPermanentPassword(password: newPW);
|
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
|
||||||
final pw = await bind.mainGetPermanentPassword();
|
|
||||||
if (newPW == pw) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchID() async {
|
fetchID() async {
|
||||||
final id = await bind.mainGetMyId();
|
final id = await bind.mainGetMyId();
|
||||||
if (id != _serverId.id) {
|
if (id != _serverId.id) {
|
||||||
|
|||||||
@@ -1159,10 +1159,6 @@ class RustdeskImpl {
|
|||||||
return Future.value('');
|
return Future.value('');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> mainGetPermanentPassword({dynamic hint}) {
|
|
||||||
return Future.value('');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> mainGetFingerprint({dynamic hint}) {
|
Future<String> mainGetFingerprint({dynamic hint}) {
|
||||||
return Future.value('');
|
return Future.value('');
|
||||||
}
|
}
|
||||||
@@ -1346,9 +1342,9 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError("mainUpdateTemporaryPassword");
|
throw UnimplementedError("mainUpdateTemporaryPassword");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> mainSetPermanentPassword(
|
Future<bool> mainSetPermanentPasswordWithResult(
|
||||||
{required String password, dynamic hint}) {
|
{required String password, dynamic hint}) {
|
||||||
throw UnimplementedError("mainSetPermanentPassword");
|
throw UnimplementedError("mainSetPermanentPasswordWithResult");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> mainCheckSuperUserPermission({dynamic hint}) {
|
Future<bool> mainCheckSuperUserPermission({dynamic hint}) {
|
||||||
|
|||||||
Submodule libs/hbb_common updated: 6fb03d076e...f08ce5d6d0
@@ -1693,8 +1693,8 @@ pub fn main_get_temporary_password() -> String {
|
|||||||
ui_interface::temporary_password()
|
ui_interface::temporary_password()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_permanent_password() -> String {
|
pub fn main_set_permanent_password_with_result(password: String) -> bool {
|
||||||
ui_interface::permanent_password()
|
ui_interface::set_permanent_password_with_result(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_fingerprint() -> String {
|
pub fn main_get_fingerprint() -> String {
|
||||||
@@ -2072,10 +2072,6 @@ pub fn main_update_temporary_password() {
|
|||||||
update_temporary_password();
|
update_temporary_password();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_set_permanent_password(password: String) {
|
|
||||||
set_permanent_password(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main_check_super_user_permission() -> bool {
|
pub fn main_check_super_user_permission() -> bool {
|
||||||
check_super_user_permission()
|
check_super_user_permission()
|
||||||
}
|
}
|
||||||
@@ -2423,16 +2419,23 @@ pub fn is_disable_installation() -> SyncReturn<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_preset_password() -> bool {
|
pub fn is_preset_password() -> bool {
|
||||||
config::HARD_SETTINGS
|
let hard = config::HARD_SETTINGS
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get("password")
|
.get("password")
|
||||||
.map_or(false, |p| {
|
.cloned()
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
.unwrap_or_default();
|
||||||
return p == &crate::ipc::get_permanent_password();
|
if hard.is_empty() {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
return false;
|
||||||
return p == &config::Config::get_permanent_password();
|
}
|
||||||
})
|
|
||||||
|
// On desktop, service owns the authoritative config; query it via IPC and return only a boolean.
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
return crate::ipc::is_permanent_password_preset();
|
||||||
|
|
||||||
|
// On mobile, we have no service IPC; verify against local storage.
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
return config::Config::matches_permanent_password_plain(&hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't call this function for desktop version.
|
// Don't call this function for desktop version.
|
||||||
@@ -2768,6 +2771,10 @@ pub fn main_get_common(key: String) -> String {
|
|||||||
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
|
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
return false.to_string();
|
return false.to_string();
|
||||||
|
} else if key == "permanent-password-set" {
|
||||||
|
return ui_interface::is_permanent_password_set().to_string();
|
||||||
|
} else if key == "local-permanent-password-set" {
|
||||||
|
return ui_interface::is_local_permanent_password_set().to_string();
|
||||||
} else {
|
} else {
|
||||||
if key.starts_with("download-data-") {
|
if key.starts_with("download-data-") {
|
||||||
let id = key.replace("download-data-", "");
|
let id = key.replace("download-data-", "");
|
||||||
|
|||||||
135
src/ipc.rs
135
src/ipc.rs
@@ -632,8 +632,29 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
value = Some(Config::get_id());
|
value = Some(Config::get_id());
|
||||||
} else if name == "temporary-password" {
|
} else if name == "temporary-password" {
|
||||||
value = Some(password::temporary_password());
|
value = Some(password::temporary_password());
|
||||||
} else if name == "permanent-password" {
|
} else if name == "permanent-password-storage-and-salt" {
|
||||||
value = Some(Config::get_permanent_password());
|
let (storage, salt) = Config::get_local_permanent_password_storage_and_salt();
|
||||||
|
value = Some(storage + "\n" + &salt);
|
||||||
|
} else if name == "permanent-password-set" {
|
||||||
|
value = Some(if Config::has_permanent_password() {
|
||||||
|
"Y".to_owned()
|
||||||
|
} else {
|
||||||
|
"N".to_owned()
|
||||||
|
});
|
||||||
|
} else if name == "permanent-password-is-preset" {
|
||||||
|
let hard = config::HARD_SETTINGS
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get("password")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let is_preset =
|
||||||
|
!hard.is_empty() && Config::matches_permanent_password_plain(&hard);
|
||||||
|
value = Some(if is_preset {
|
||||||
|
"Y".to_owned()
|
||||||
|
} else {
|
||||||
|
"N".to_owned()
|
||||||
|
});
|
||||||
} else if name == "salt" {
|
} else if name == "salt" {
|
||||||
value = Some(Config::get_salt());
|
value = Some(Config::get_salt());
|
||||||
} else if name == "rendezvous_server" {
|
} else if name == "rendezvous_server" {
|
||||||
@@ -669,13 +690,24 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
allow_err!(stream.send(&Data::Config((name, value))).await);
|
allow_err!(stream.send(&Data::Config((name, value))).await);
|
||||||
}
|
}
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
|
let mut updated = true;
|
||||||
if name == "id" {
|
if name == "id" {
|
||||||
Config::set_key_confirmed(false);
|
Config::set_key_confirmed(false);
|
||||||
Config::set_id(&value);
|
Config::set_id(&value);
|
||||||
} else if name == "temporary-password" {
|
} else if name == "temporary-password" {
|
||||||
password::update_temporary_password();
|
password::update_temporary_password();
|
||||||
} else if name == "permanent-password" {
|
} else if name == "permanent-password" {
|
||||||
Config::set_permanent_password(&value);
|
if Config::is_disable_change_permanent_password() {
|
||||||
|
log::warn!("Changing permanent password is disabled");
|
||||||
|
updated = false;
|
||||||
|
} else {
|
||||||
|
Config::set_permanent_password(&value);
|
||||||
|
}
|
||||||
|
// Explicitly ACK/NACK permanent-password writes. This allows UIs/FFI to
|
||||||
|
// distinguish "accepted by daemon" vs "IPC send succeeded" without
|
||||||
|
// reading back any secret.
|
||||||
|
let ack = if updated { "Y" } else { "N" }.to_owned();
|
||||||
|
allow_err!(stream.send(&Data::Config((name.clone(), Some(ack)))).await);
|
||||||
} else if name == "salt" {
|
} else if name == "salt" {
|
||||||
Config::set_salt(&value);
|
Config::set_salt(&value);
|
||||||
} else if name == "voice-call-input" {
|
} else if name == "voice-call-input" {
|
||||||
@@ -685,7 +717,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log::info!("{} updated", name);
|
if updated {
|
||||||
|
log::info!("{} updated", name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Data::Options(value) => match value {
|
Data::Options(value) => match value {
|
||||||
@@ -1143,13 +1177,57 @@ pub fn update_temporary_password() -> ResultType<()> {
|
|||||||
set_config("temporary-password", "".to_owned())
|
set_config("temporary-password", "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_permanent_password() -> String {
|
fn apply_permanent_password_storage_and_salt_payload(payload: Option<&str>) -> ResultType<()> {
|
||||||
if let Ok(Some(v)) = get_config("permanent-password") {
|
let Some(payload) = payload else {
|
||||||
Config::set_permanent_password(&v);
|
return Ok(());
|
||||||
v
|
};
|
||||||
} else {
|
let Some((storage, salt)) = payload.split_once('\n') else {
|
||||||
Config::get_permanent_password()
|
bail!("Invalid permanent-password-storage-and-salt payload");
|
||||||
|
};
|
||||||
|
|
||||||
|
if storage.is_empty() {
|
||||||
|
Config::set_permanent_password_storage_for_sync("", "")?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Config::set_permanent_password_storage_for_sync(storage, salt)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_permanent_password_storage_from_daemon() -> ResultType<()> {
|
||||||
|
let v = get_config("permanent-password-storage-and-salt")?;
|
||||||
|
apply_permanent_password_storage_and_salt_payload(v.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_permanent_password_storage_from_daemon_async() -> ResultType<()> {
|
||||||
|
let ms_timeout = 1_000;
|
||||||
|
let v = get_config_async("permanent-password-storage-and-salt", ms_timeout).await?;
|
||||||
|
apply_permanent_password_storage_and_salt_payload(v.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_permanent_password_set() -> bool {
|
||||||
|
match get_config("permanent-password-set") {
|
||||||
|
Ok(Some(v)) => {
|
||||||
|
let v = v.trim();
|
||||||
|
return v == "Y";
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
// No response/value (timeout).
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// Connection error.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::warn!("Failed to query permanent password state from daemon");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_permanent_password_preset() -> bool {
|
||||||
|
if let Ok(Some(v)) = get_config("permanent-password-is-preset") {
|
||||||
|
let v = v.trim();
|
||||||
|
return v == "Y";
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fingerprint() -> String {
|
pub fn get_fingerprint() -> String {
|
||||||
@@ -1159,8 +1237,41 @@ pub fn get_fingerprint() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_permanent_password(v: String) -> ResultType<()> {
|
pub fn set_permanent_password(v: String) -> ResultType<()> {
|
||||||
Config::set_permanent_password(&v);
|
if Config::is_disable_change_permanent_password() {
|
||||||
set_config("permanent-password", v)
|
bail!("Changing permanent password is disabled");
|
||||||
|
}
|
||||||
|
if set_permanent_password_with_ack(v)? {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("Changing permanent password was rejected by daemon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn set_permanent_password_with_ack(v: String) -> ResultType<bool> {
|
||||||
|
set_permanent_password_with_ack_async(v).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_permanent_password_with_ack_async(v: String) -> ResultType<bool> {
|
||||||
|
// The daemon ACK/NACK is expected quickly since it applies the config in-process.
|
||||||
|
let ms_timeout = 1_000;
|
||||||
|
let mut c = connect(ms_timeout, "").await?;
|
||||||
|
c.send_config("permanent-password", v).await?;
|
||||||
|
if let Some(Data::Config((name2, Some(v)))) = c.next_timeout(ms_timeout).await? {
|
||||||
|
if name2 == "permanent-password" {
|
||||||
|
let v = v.trim();
|
||||||
|
let ok = v == "Y";
|
||||||
|
if ok {
|
||||||
|
// Ensure the hashed permanent password storage is written to the user config file.
|
||||||
|
// This sync must not affect the daemon ACK outcome.
|
||||||
|
if let Err(err) = sync_permanent_password_storage_from_daemon_async().await {
|
||||||
|
log::warn!("Failed to sync permanent password storage from daemon: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "flutter")]
|
#[cfg(feature = "flutter")]
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "متابعة مع {}"),
|
("Continue with {}", "متابعة مع {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Працягнуць з {}"),
|
("Continue with {}", "Працягнуць з {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Продължи с {}"),
|
("Continue with {}", "Продължи с {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Continua amb {}"),
|
("Continue with {}", "Continua amb {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "传入会话期间保持屏幕常亮"),
|
("keep-awake-during-incoming-sessions-label", "传入会话期间保持屏幕常亮"),
|
||||||
("Continue with {}", "使用 {} 登录"),
|
("Continue with {}", "使用 {} 登录"),
|
||||||
("Display Name", "显示名称"),
|
("Display Name", "显示名称"),
|
||||||
|
("password-hidden-tip", "永久密码已设置(已隐藏)"),
|
||||||
|
("preset-password-in-use-tip", "当前使用预设密码"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Pokračovat s {}"),
|
("Continue with {}", "Pokračovat s {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Fortsæt med {}"),
|
("Continue with {}", "Fortsæt med {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Bildschirm während eingehender Sitzungen aktiv halten"),
|
("keep-awake-during-incoming-sessions-label", "Bildschirm während eingehender Sitzungen aktiv halten"),
|
||||||
("Continue with {}", "Fortfahren mit {}"),
|
("Continue with {}", "Fortfahren mit {}"),
|
||||||
("Display Name", "Anzeigename"),
|
("Display Name", "Anzeigename"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Διατήρηση ενεργής οθόνης κατά τη διάρκεια των εισερχόμενων συνεδριών"),
|
("keep-awake-during-incoming-sessions-label", "Διατήρηση ενεργής οθόνης κατά τη διάρκεια των εισερχόμενων συνεδριών"),
|
||||||
("Continue with {}", "Συνέχεια με {}"),
|
("Continue with {}", "Συνέχεια με {}"),
|
||||||
("Display Name", "Εμφανιζόμενο όνομα"),
|
("Display Name", "Εμφανιζόμενο όνομα"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,5 +272,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("rel-mouse-permission-lost-tip", "Keyboard permission was revoked. Relative Mouse Mode has been disabled."),
|
("rel-mouse-permission-lost-tip", "Keyboard permission was revoked. Relative Mouse Mode has been disabled."),
|
||||||
("keep-awake-during-outgoing-sessions-label", "Keep screen awake during outgoing sessions"),
|
("keep-awake-during-outgoing-sessions-label", "Keep screen awake during outgoing sessions"),
|
||||||
("keep-awake-during-incoming-sessions-label", "Keep screen awake during incoming sessions"),
|
("keep-awake-during-incoming-sessions-label", "Keep screen awake during incoming sessions"),
|
||||||
|
("password-hidden-tip", "Permanent password is set (hidden)."),
|
||||||
|
("preset-password-in-use-tip", "Preset password is currently in use."),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", ""),
|
("Continue with {}", ""),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Continuar con {}"),
|
("Continue with {}", "Continuar con {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Jätka koos {}"),
|
("Continue with {}", "Jätka koos {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "{} honekin jarraitu"),
|
("Continue with {}", "{} honekin jarraitu"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "ادامه با {}"),
|
("Continue with {}", "ادامه با {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Jatka käyttäen {}"),
|
("Continue with {}", "Jatka käyttäen {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Maintenir l’écran allumé lors des sessions entrantes"),
|
("keep-awake-during-incoming-sessions-label", "Maintenir l’écran allumé lors des sessions entrantes"),
|
||||||
("Continue with {}", "Continuer avec {}"),
|
("Continue with {}", "Continuer avec {}"),
|
||||||
("Display Name", "Nom d’affichage"),
|
("Display Name", "Nom d’affichage"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "{}-ით გაგრძელება"),
|
("Continue with {}", "{}-ით გაგრძელება"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "המשך עם {}"),
|
("Continue with {}", "המשך עם {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Nastavi sa {}"),
|
("Continue with {}", "Nastavi sa {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Képernyő aktív állapotban tartása a bejövő munkamenetek során"),
|
("keep-awake-during-incoming-sessions-label", "Képernyő aktív állapotban tartása a bejövő munkamenetek során"),
|
||||||
("Continue with {}", "Folytatás ezzel: {}"),
|
("Continue with {}", "Folytatás ezzel: {}"),
|
||||||
("Display Name", "Kijelző név"),
|
("Display Name", "Kijelző név"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Lanjutkan dengan {}"),
|
("Continue with {}", "Lanjutkan dengan {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Mantieni lo schermo attivo durante le sessioni in ingresso"),
|
("keep-awake-during-incoming-sessions-label", "Mantieni lo schermo attivo durante le sessioni in ingresso"),
|
||||||
("Continue with {}", "Continua con {}"),
|
("Continue with {}", "Continua con {}"),
|
||||||
("Display Name", "Visualizza nome"),
|
("Display Name", "Visualizza nome"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "{} で続行"),
|
("Continue with {}", "{} で続行"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
||||||
("Continue with {}", "{}(으)로 계속"),
|
("Continue with {}", "{}(으)로 계속"),
|
||||||
("Display Name", "표시 이름"),
|
("Display Name", "표시 이름"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", ""),
|
("Continue with {}", ""),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Tęsti su {}"),
|
("Continue with {}", "Tęsti su {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Turpināt ar {}"),
|
("Continue with {}", "Turpināt ar {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Fortsett med {}"),
|
("Continue with {}", "Fortsett med {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Houd het scherm open tijdens de inkomende sessies."),
|
("keep-awake-during-incoming-sessions-label", "Houd het scherm open tijdens de inkomende sessies."),
|
||||||
("Continue with {}", "Ga verder met {}"),
|
("Continue with {}", "Ga verder met {}"),
|
||||||
("Display Name", "Naam Weergeven"),
|
("Display Name", "Naam Weergeven"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji przychodzących"),
|
("keep-awake-during-incoming-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji przychodzących"),
|
||||||
("Continue with {}", "Kontynuuj z {}"),
|
("Continue with {}", "Kontynuuj z {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", ""),
|
("Continue with {}", ""),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"),
|
("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"),
|
||||||
("Continue with {}", "Continuar com {}"),
|
("Continue with {}", "Continuar com {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Continuă cu {}"),
|
("Continue with {}", "Continuă cu {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Не отключать экран во время входящих сеансов"),
|
("keep-awake-during-incoming-sessions-label", "Не отключать экран во время входящих сеансов"),
|
||||||
("Continue with {}", "Продолжить с {}"),
|
("Continue with {}", "Продолжить с {}"),
|
||||||
("Display Name", "Отображаемое имя"),
|
("Display Name", "Отображаемое имя"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Sighi cun {}"),
|
("Continue with {}", "Sighi cun {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Pokračovať s {}"),
|
("Continue with {}", "Pokračovať s {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Nadaljuj z {}"),
|
("Continue with {}", "Nadaljuj z {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Vazhdo me {}"),
|
("Continue with {}", "Vazhdo me {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Nastavi sa {}"),
|
("Continue with {}", "Nastavi sa {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Fortsätt med {}"),
|
("Continue with {}", "Fortsätt med {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "{} உடன் தொடர்"),
|
("Continue with {}", "{} உடன் தொடர்"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", ""),
|
("Continue with {}", ""),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "ทำต่อด้วย {}"),
|
("Continue with {}", "ทำต่อด้วย {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranı açık tutun"),
|
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranı açık tutun"),
|
||||||
("Continue with {}", "{} ile devam et"),
|
("Continue with {}", "{} ile devam et"),
|
||||||
("Display Name", "Görünen Ad"),
|
("Display Name", "Görünen Ad"),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
|
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
|
||||||
("Continue with {}", "使用 {} 登入"),
|
("Continue with {}", "使用 {} 登入"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Продовжити з {}"),
|
("Continue with {}", "Продовжити з {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("keep-awake-during-incoming-sessions-label", ""),
|
("keep-awake-during-incoming-sessions-label", ""),
|
||||||
("Continue with {}", "Tiếp tục với {}"),
|
("Continue with {}", "Tiếp tục với {}"),
|
||||||
("Display Name", ""),
|
("Display Name", ""),
|
||||||
|
("password-hidden-tip", ""),
|
||||||
|
("preset-password-in-use-tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use hbb_common::platform::linux::run_cmds;
|
|||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use hbb_common::protobuf::EnumOrUnknown;
|
use hbb_common::protobuf::EnumOrUnknown;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
|
config::decode_permanent_password_h1_from_storage,
|
||||||
config::{self, keys, Config, TrustedDevice},
|
config::{self, keys, Config, TrustedDevice},
|
||||||
fs::{self, can_enable_overwrite_detection, JobType},
|
fs::{self, can_enable_overwrite_detection, JobType},
|
||||||
futures::{SinkExt, StreamExt},
|
futures::{SinkExt, StreamExt},
|
||||||
@@ -77,6 +78,18 @@ lazy_static::lazy_static! {
|
|||||||
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
|
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
|
||||||
|
if a.len() != b.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Avoid data-dependent early exits.
|
||||||
|
let mut x: u8 = 0;
|
||||||
|
for i in 0..a.len() {
|
||||||
|
x |= a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
x == 0
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref WALLPAPER_REMOVER: Arc<Mutex<Option<WallPaperRemover>>> = Default::default();
|
static ref WALLPAPER_REMOVER: Arc<Mutex<Option<WallPaperRemover>>> = Default::default();
|
||||||
@@ -1969,23 +1982,53 @@ impl Connection {
|
|||||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_one_password(&self, password: String) -> bool {
|
fn verify_h1(&self, h1: &[u8]) -> bool {
|
||||||
if password.len() == 0 {
|
let mut hasher2 = Sha256::new();
|
||||||
|
hasher2.update(h1);
|
||||||
|
hasher2.update(self.hash.challenge.as_bytes());
|
||||||
|
// A normal `==` on slices may short-circuit on the first mismatch, which can leak how many leading
|
||||||
|
// bytes matched via timing. In typical remote scenarios this is difficult to exploit due to network
|
||||||
|
// jitter, changing challenges, and login attempt throttling, but a constant-time comparison here is
|
||||||
|
// low-cost defensive programming.
|
||||||
|
constant_time_eq(&hasher2.finalize()[..], &self.lr.password[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn validate_one_password(&self, password: &str) -> bool {
|
||||||
|
self.validate_password_plain(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_password_plain(&self, password: &str) -> bool {
|
||||||
|
if password.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(password);
|
hasher.update(password.as_bytes());
|
||||||
hasher.update(&self.hash.salt);
|
hasher.update(self.hash.salt.as_bytes());
|
||||||
let mut hasher2 = Sha256::new();
|
let h1_plain = hasher.finalize();
|
||||||
hasher2.update(&hasher.finalize()[..]);
|
self.verify_h1(&h1_plain[..])
|
||||||
hasher2.update(&self.hash.challenge);
|
}
|
||||||
hasher2.finalize()[..] == self.lr.password[..]
|
|
||||||
|
fn validate_password_storage(&self, storage: &str) -> bool {
|
||||||
|
if storage.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use strict decode success to detect hashed storage.
|
||||||
|
// If decode fails, treat as legacy plaintext storage for compatibility.
|
||||||
|
if let Some(h1) = decode_permanent_password_h1_from_storage(storage) {
|
||||||
|
return self.verify_h1(&h1[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy plaintext storage path.
|
||||||
|
self.validate_password_plain(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_password(&mut self) -> bool {
|
fn validate_password(&mut self) -> bool {
|
||||||
if password::temporary_enabled() {
|
if password::temporary_enabled() {
|
||||||
let password = password::temporary_password();
|
let password = password::temporary_password();
|
||||||
if self.validate_one_password(password.clone()) {
|
if self.validate_one_password(&password) {
|
||||||
raii::AuthedConnID::update_or_insert_session(
|
raii::AuthedConnID::update_or_insert_session(
|
||||||
self.session_key(),
|
self.session_key(),
|
||||||
Some(password),
|
Some(password),
|
||||||
@@ -1995,8 +2038,24 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if password::permanent_enabled() {
|
if password::permanent_enabled() {
|
||||||
if self.validate_one_password(Config::get_permanent_password()) {
|
// Since hashed storage uses a prefix-based encoding, a hard plaintext that
|
||||||
return true;
|
// happens to look like hashed storage could be mis-detected. Validate local storage
|
||||||
|
// and hard/preset plaintext via separate paths to avoid that ambiguity.
|
||||||
|
let (local_storage, _) = Config::get_local_permanent_password_storage_and_salt();
|
||||||
|
if !local_storage.is_empty() {
|
||||||
|
if self.validate_password_storage(&local_storage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hard = config::HARD_SETTINGS
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get("password")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !hard.is_empty() && self.validate_password_plain(&hard) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@@ -2016,7 +2075,7 @@ impl Connection {
|
|||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
if !self.lr.password.is_empty()
|
if !self.lr.password.is_empty()
|
||||||
&& (tfa && session.tfa
|
&& (tfa && session.tfa
|
||||||
|| !tfa && self.validate_one_password(session.random_password.clone()))
|
|| !tfa && self.validate_password_plain(&session.random_password))
|
||||||
{
|
{
|
||||||
log::info!("is recent session");
|
log::info!("is recent session");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
15
src/ui.rs
15
src/ui.rs
@@ -212,12 +212,16 @@ impl UI {
|
|||||||
update_temporary_password()
|
update_temporary_password()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permanent_password(&self) -> String {
|
fn set_permanent_password(&self, password: String) {
|
||||||
permanent_password()
|
let _ = set_permanent_password_with_result(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_permanent_password(&self, password: String) {
|
fn is_local_permanent_password_set(&self) -> bool {
|
||||||
set_permanent_password(password);
|
is_local_permanent_password_set()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_permanent_password_set(&self) -> bool {
|
||||||
|
is_permanent_password_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_remote_id(&mut self) -> String {
|
fn get_remote_id(&mut self) -> String {
|
||||||
@@ -726,8 +730,9 @@ impl sciter::EventHandler for UI {
|
|||||||
fn get_id();
|
fn get_id();
|
||||||
fn temporary_password();
|
fn temporary_password();
|
||||||
fn update_temporary_password();
|
fn update_temporary_password();
|
||||||
fn permanent_password();
|
|
||||||
fn set_permanent_password(String);
|
fn set_permanent_password(String);
|
||||||
|
fn is_local_permanent_password_set();
|
||||||
|
fn is_permanent_password_set();
|
||||||
fn get_remote_id();
|
fn get_remote_id();
|
||||||
fn set_remote_id(String);
|
fn set_remote_id(String);
|
||||||
fn closing(i32, i32, i32, i32);
|
fn closing(i32, i32, i32, i32);
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ button.button:hover, button.outline:hover {
|
|||||||
border-color: color(hover-border);
|
border-color: color(hover-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:disabled,
|
||||||
|
button:disabled:hover {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
button.link {
|
button.link {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -484,4 +489,4 @@ div.user-session select {
|
|||||||
background: color(bg);
|
background: color(bg);
|
||||||
color: color(text);
|
color: color(text);
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1072,6 +1072,7 @@ class PasswordArea: Reactor.Component {
|
|||||||
var method = handler.get_option('verification-method');
|
var method = handler.get_option('verification-method');
|
||||||
var approve_mode= handler.get_option('approve-mode');
|
var approve_mode= handler.get_option('approve-mode');
|
||||||
var show_password = approve_mode != 'click';
|
var show_password = approve_mode != 'click';
|
||||||
|
var has_local_password = handler.is_local_permanent_password_set();
|
||||||
return <popup><menu.context #edit-password-context>
|
return <popup><menu.context #edit-password-context>
|
||||||
<li #approve-mode-password><span>{svg_checkmark}</span>{translate('Accept sessions via password')}</li>
|
<li #approve-mode-password><span>{svg_checkmark}</span>{translate('Accept sessions via password')}</li>
|
||||||
<li #approve-mode-click><span>{svg_checkmark}</span>{translate('Accept sessions via click')}</li>
|
<li #approve-mode-click><span>{svg_checkmark}</span>{translate('Accept sessions via click')}</li>
|
||||||
@@ -1082,6 +1083,7 @@ class PasswordArea: Reactor.Component {
|
|||||||
{ !show_password ? '' : <li #use-both-passwords><span>{svg_checkmark}</span>{translate('Use both passwords')}</li> }
|
{ !show_password ? '' : <li #use-both-passwords><span>{svg_checkmark}</span>{translate('Use both passwords')}</li> }
|
||||||
{ !show_password ? '' : <div .separator /> }
|
{ !show_password ? '' : <div .separator /> }
|
||||||
{ !show_password || disable_change_permanent_password ? '' : <li #set-password disabled={ method == 'use-temporary-password' ? "true" : "false" }>{translate('Set permanent password')}</li> }
|
{ !show_password || disable_change_permanent_password ? '' : <li #set-password disabled={ method == 'use-temporary-password' ? "true" : "false" }>{translate('Set permanent password')}</li> }
|
||||||
|
{ !show_password || disable_change_permanent_password ? '' : <li #clear-password disabled={ has_local_password ? "false" : "true" }>{translate('Clear permanent password')}</li> }
|
||||||
{ !show_password ? '' : <TemporaryPasswordLengthMenu /> }
|
{ !show_password ? '' : <TemporaryPasswordLengthMenu /> }
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #tfa><span>{svg_checkmark}</span>{translate('enable-2fa-title')}</li>
|
<li #tfa><span>{svg_checkmark}</span>{translate('enable-2fa-title')}</li>
|
||||||
@@ -1114,6 +1116,10 @@ class PasswordArea: Reactor.Component {
|
|||||||
el.state.disabled = true;
|
el.state.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (el.id == "clear-password") {
|
||||||
|
var has_local_password = handler.is_local_permanent_password_set();
|
||||||
|
el.state.disabled = !has_local_password;
|
||||||
|
}
|
||||||
if (el.id == "tfa")
|
if (el.id == "tfa")
|
||||||
el.attributes.toggleClass("selected", has_valid_2fa);
|
el.attributes.toggleClass("selected", has_valid_2fa);
|
||||||
}
|
}
|
||||||
@@ -1129,16 +1135,28 @@ class PasswordArea: Reactor.Component {
|
|||||||
|
|
||||||
event click $(li#set-password) {
|
event click $(li#set-password) {
|
||||||
var me = this;
|
var me = this;
|
||||||
var password = handler.permanent_password();
|
var has_local_password = handler.is_local_permanent_password_set();
|
||||||
var value_field = password.length == 0 ? "" : "value=" + password;
|
var permanent_password_set = handler.is_permanent_password_set();
|
||||||
|
var password_hidden_tip = translate('password-hidden-tip');
|
||||||
|
var preset_password_tip = translate('preset-password-in-use-tip');
|
||||||
|
var password_tip = "";
|
||||||
|
if (has_local_password) {
|
||||||
|
password_tip = "<div style='margin-top:0.5em; color:#e5a43a;'><span style='font-weight:bold;'>[!]</span> " + password_hidden_tip + "</div>";
|
||||||
|
} else if (permanent_password_set) {
|
||||||
|
password_tip = "<div style='margin-top:0.5em; color:#e5a43a;'><span style='font-weight:bold;'>[!]</span> " + preset_password_tip + "</div>";
|
||||||
|
}
|
||||||
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
||||||
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus " + value_field + " /></div> \
|
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
|
||||||
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) " + value_field + " /></div> \
|
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
|
||||||
|
" + password_tip + " \
|
||||||
</div> \
|
</div> \
|
||||||
", "", function(res=null) {
|
", "", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
var p0 = (res.password || "").trim();
|
var p0 = (res.password || "").trim();
|
||||||
var p1 = (res.confirmation || "").trim();
|
var p1 = (res.confirmation || "").trim();
|
||||||
|
if (p0.length == 0 && p1.length == 0) {
|
||||||
|
return " ";
|
||||||
|
}
|
||||||
if (p0.length < 6 && p0.length != 0) {
|
if (p0.length < 6 && p0.length != 0) {
|
||||||
return translate("Too short, at least 6 characters.");
|
return translate("Too short, at least 6 characters.");
|
||||||
}
|
}
|
||||||
@@ -1148,6 +1166,15 @@ class PasswordArea: Reactor.Component {
|
|||||||
handler.set_permanent_password(p0);
|
handler.set_permanent_password(p0);
|
||||||
me.update();
|
me.update();
|
||||||
}, msgbox_default_height, get_msgbox_width());
|
}, msgbox_default_height, get_msgbox_width());
|
||||||
|
self.timer(30ms, function() {
|
||||||
|
updateSetPasswordSubmitState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(li#clear-password) {
|
||||||
|
if (this.$(li#clear-password).state.disabled) return;
|
||||||
|
handler.set_permanent_password("");
|
||||||
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(menu#edit-password-context>li) (_, me) {
|
event click $(menu#edit-password-context>li) (_, me) {
|
||||||
@@ -1227,6 +1254,18 @@ function updatePasswordArea() {
|
|||||||
}
|
}
|
||||||
if (!outgoing_only) updatePasswordArea();
|
if (!outgoing_only) updatePasswordArea();
|
||||||
|
|
||||||
|
function updateSetPasswordSubmitState() {
|
||||||
|
var dialog = $(#msgbox);
|
||||||
|
if (!dialog) return;
|
||||||
|
var password = dialog.$(input[name='password']);
|
||||||
|
var confirmation = dialog.$(input[name='confirmation']);
|
||||||
|
var submit = dialog.$(button#submit);
|
||||||
|
if (!password || !confirmation || !submit) return;
|
||||||
|
var can_submit = (password.value || "").trim().length > 0 ||
|
||||||
|
(confirmation.value || "").trim().length > 0;
|
||||||
|
submit.state.disabled = !can_submit;
|
||||||
|
}
|
||||||
|
|
||||||
class ID: Reactor.Component {
|
class ID: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")}
|
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")}
|
||||||
@@ -1284,6 +1323,22 @@ event keydown (evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event keyup $(#msgbox input[name='password']) {
|
||||||
|
updateSetPasswordSubmitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
event keyup $(#msgbox input[name='confirmation']) {
|
||||||
|
updateSetPasswordSubmitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
event change $(#msgbox input[name='password']) {
|
||||||
|
updateSetPasswordSubmitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
event change $(#msgbox input[name='confirmation']) {
|
||||||
|
updateSetPasswordSubmitState();
|
||||||
|
}
|
||||||
|
|
||||||
$(body).content(<div style="size:*"><App /><div #msgbox /></div>);
|
$(body).content(<div style="size:*"><App /><div #msgbox /></div>);
|
||||||
|
|
||||||
event click $(#powered-by) {
|
event click $(#powered-by) {
|
||||||
|
|||||||
@@ -193,8 +193,10 @@ class MsgboxComponent: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
if (this.$(button#submit)) {
|
var submit_btn = this.$(button#submit);
|
||||||
this.$(button#submit).sendEvent("click");
|
if (submit_btn) {
|
||||||
|
if (submit_btn.state.disabled) return;
|
||||||
|
submit_btn.sendEvent("click");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -609,19 +609,57 @@ pub fn update_temporary_password() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn permanent_password() -> String {
|
pub fn is_permanent_password_set() -> bool {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
return Config::get_permanent_password();
|
return Config::has_permanent_password();
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
return ipc::get_permanent_password();
|
{
|
||||||
|
let daemon_is_set = ipc::is_permanent_password_set();
|
||||||
|
// `daemon_is_set` is authoritative for the return value. Local storage is only used to
|
||||||
|
// decide whether we should attempt a sync to clear stale user-side state.
|
||||||
|
let local_storage_is_empty = if daemon_is_set {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let (storage, _) = Config::get_local_permanent_password_storage_and_salt();
|
||||||
|
storage.is_empty()
|
||||||
|
};
|
||||||
|
if daemon_is_set || !local_storage_is_empty {
|
||||||
|
allow_err!(ipc::sync_permanent_password_storage_from_daemon());
|
||||||
|
}
|
||||||
|
daemon_is_set
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_permanent_password(password: String) {
|
pub fn is_local_permanent_password_set() -> bool {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
Config::set_permanent_password(&password);
|
return Config::has_local_permanent_password();
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
allow_err!(ipc::set_permanent_password(password));
|
{
|
||||||
|
allow_err!(ipc::sync_permanent_password_storage_from_daemon());
|
||||||
|
Config::has_local_permanent_password()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_permanent_password_with_result(password: String) -> bool {
|
||||||
|
if config::Config::is_disable_change_permanent_password() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
{
|
||||||
|
config::Config::set_permanent_password(&password);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
match crate::ipc::set_permanent_password_with_ack(password) {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Failed to set permanent password via IPC: {err}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user