password: safe/random personal password

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2022-06-20 10:41:46 +08:00
parent 2851d71290
commit e46019a171
31 changed files with 1060 additions and 94 deletions

1
Cargo.lock generated
View File

@ -2087,6 +2087,7 @@ dependencies = [
"lazy_static",
"log",
"mac_address",
"machine-uid",
"protobuf",
"protobuf-codegen",
"quinn",

View File

@ -34,6 +34,7 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1"
machine-uid = "0.2"
[features]
quic = []

View File

@ -1,4 +1,8 @@
use crate::log;
use crate::{
log,
password_security::config::{decrypt_str_or_original, encrypt_str_or_original},
};
use anyhow::Result;
use directories_next::ProjectDirs;
use rand::Rng;
use serde_derive::{Deserialize, Serialize};
@ -17,6 +21,7 @@ pub const CONNECT_TIMEOUT: u64 = 18_000;
pub const REG_INTERVAL: i64 = 12_000;
pub const COMPRESS_LEVEL: i32 = 3;
const SERIAL: i32 = 3;
const PASSWORD_ENC_VERSION: &'static str = "00";
// 128x128
#[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII=
@ -267,11 +272,19 @@ impl Config {
}
fn load() -> Config {
Config::load_::<Config>("")
let mut config = Config::load_::<Config>("");
let (password, store) = decrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
config.password = password;
if store {
config.store();
}
config
}
fn store(&self) {
Config::store_(self, "");
let mut config = self.clone();
config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
Config::store_(&config, "");
}
pub fn file() -> PathBuf {
@ -627,7 +640,7 @@ impl Config {
log::info!("id updated from {} to {}", id, new_id);
}
pub fn set_password(password: &str) {
pub fn set_security_password(password: &str) {
let mut config = CONFIG.write().unwrap();
if password == config.password {
return;
@ -636,13 +649,8 @@ impl Config {
config.store();
}
pub fn get_password() -> String {
let mut password = CONFIG.read().unwrap().password.clone();
if password.is_empty() {
password = Config::get_auto_password();
Config::set_password(&password);
}
password
pub fn get_security_password() -> String {
CONFIG.read().unwrap().password.clone()
}
pub fn set_salt(salt: &str) {

View File

@ -3,6 +3,7 @@ pub mod protos;
pub use protos::message as message_proto;
pub use protos::rendezvous as rendezvous_proto;
pub use bytes;
use config::Config;
pub use futures;
pub use protobuf;
use std::{
@ -26,6 +27,7 @@ pub use anyhow::{self, bail};
pub use futures_util;
pub mod config;
pub mod fs;
pub use lazy_static;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use mac_address;
pub use rand;
@ -34,7 +36,7 @@ pub use sodiumoxide;
pub use tokio_socks;
pub use tokio_socks::IntoTargetAddr;
pub use tokio_socks::TargetAddr;
pub use lazy_static;
pub mod password_security;
#[cfg(feature = "quic")]
pub type Stream = quic::Connection;
@ -199,6 +201,14 @@ pub fn get_modified_time(path: &std::path::Path) -> SystemTime {
.unwrap_or(UNIX_EPOCH)
}
pub fn get_uuid() -> Vec<u8> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(id) = machine_uid::get() {
return id.into();
}
Config::get_key_pair().1
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -0,0 +1,330 @@
pub mod password {
use crate::config::Config;
use std::{
fmt::Display,
str::FromStr,
sync::{Arc, RwLock},
};
lazy_static::lazy_static! {
pub static ref RANDOM_PASSWORD:Arc<RwLock<String>> = Arc::new(RwLock::new(Config::get_auto_password()));
}
const SECURITY_ENABLED: &'static str = "security-password-enabled";
const RANDOM_ENABLED: &'static str = "random-password-enabled";
const ONETIME_ENABLED: &'static str = "onetime-password-enabled";
const ONETIME_ACTIVATED: &'static str = "onetime-password-activated";
const UPDATE_METHOD: &'static str = "random-password-update-method";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpdateMethod {
KEEP,
UPDATE,
DISABLE,
}
impl FromStr for UpdateMethod {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "KEEP" {
Ok(Self::KEEP)
} else if s == "UPDATE" {
Ok(Self::UPDATE)
} else if s == "DISABLE" {
Ok(Self::DISABLE)
} else {
Err(())
}
}
}
impl Display for UpdateMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UpdateMethod::KEEP => write!(f, "KEEP"),
UpdateMethod::UPDATE => write!(f, "UPDATE"),
UpdateMethod::DISABLE => write!(f, "DISABLE"),
}
}
}
pub fn set_random_password(password: &str) {
*RANDOM_PASSWORD.write().unwrap() = password.to_owned();
}
pub fn random_password() -> String {
let mut password = RANDOM_PASSWORD.read().unwrap().clone();
if password.is_empty() {
password = Config::get_auto_password();
set_random_password(&password);
}
password
}
pub fn random_password_valid() -> bool {
if random_enabled() {
onetime_password_activated() || !onetime_password_enabled()
} else {
false
}
}
pub fn passwords() -> Vec<String> {
let mut v = vec![];
if random_password_valid() {
v.push(random_password());
}
if security_enabled() {
v.push(Config::get_security_password());
}
v
}
pub fn after_session(authorized: bool) {
if authorized && random_enabled() {
UpdateMethod::from_str(&update_method())
.map(|method| match method {
UpdateMethod::KEEP => {}
UpdateMethod::UPDATE => set_random_password(&Config::get_auto_password()),
UpdateMethod::DISABLE => set_random_enabled(false),
})
.ok();
}
}
pub fn update_method() -> String {
let mut method = Config::get_option(UPDATE_METHOD);
if UpdateMethod::from_str(&method).is_err() {
method = UpdateMethod::KEEP.to_string(); // default is keep
set_update_method(&method);
}
method
}
pub fn set_update_method(method: &str) {
Config::set_option(UPDATE_METHOD.to_owned(), method.to_owned());
}
pub fn random_enabled() -> bool {
str2bool(RANDOM_ENABLED, true, || {
set_onetime_password_activated(false);
set_random_password(&Config::get_auto_password());
})
}
pub fn set_random_enabled(enabled: bool) {
if enabled != random_enabled() {
Config::set_option(RANDOM_ENABLED.to_owned(), bool2str(enabled));
set_onetime_password_activated(false);
if enabled {
set_random_password(&Config::get_auto_password());
}
}
}
pub fn security_enabled() -> bool {
str2bool(SECURITY_ENABLED, true, || {})
}
pub fn set_security_enabled(enabled: bool) {
if enabled != security_enabled() {
Config::set_option(SECURITY_ENABLED.to_owned(), bool2str(enabled));
}
}
pub fn onetime_password_enabled() -> bool {
str2bool(ONETIME_ENABLED, false, || {
set_onetime_password_activated(false);
set_random_password(&Config::get_auto_password());
})
}
pub fn set_onetime_password_enabled(enabled: bool) {
if enabled != onetime_password_enabled() {
Config::set_option(ONETIME_ENABLED.to_owned(), bool2str(enabled));
set_onetime_password_activated(false);
set_random_password(&Config::get_auto_password());
}
}
pub fn onetime_password_activated() -> bool {
str2bool(ONETIME_ACTIVATED, false, || {})
}
pub fn set_onetime_password_activated(activated: bool) {
if activated != onetime_password_activated() {
Config::set_option(ONETIME_ACTIVATED.to_owned(), bool2str(activated));
if activated {
set_random_password(&Config::get_auto_password());
}
}
}
// notice: Function nesting
fn str2bool(key: &str, default: bool, default_set: impl Fn()) -> bool {
let option = Config::get_option(key);
if option == "Y" {
true
} else if option == "N" {
false
} else {
Config::set_option(key.to_owned(), bool2str(default));
default_set();
default
}
}
fn bool2str(option: bool) -> String {
if option { "Y" } else { "N" }.to_owned()
}
}
pub mod config {
use super::base64::decrypt as decrypt00;
use super::base64::encrypt as encrypt00;
const VERSION_LEN: usize = 2;
pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
if version.len() == VERSION_LEN {
if version == "00" {
if let Ok(s) = encrypt00(s.as_bytes()) {
return version.to_owned() + &s;
}
}
}
s.to_owned()
}
// bool: whether should store to re-encrypt when load
pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool) {
if s.len() > VERSION_LEN {
let version = &s[..VERSION_LEN];
if version == "00" {
if let Ok(v) = decrypt00(&s[VERSION_LEN..].as_bytes()) {
return (
String::from_utf8_lossy(&v).to_string(),
version != current_version,
);
}
}
}
(s.to_owned(), !s.is_empty())
}
pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec<u8> {
if version.len() == VERSION_LEN {
if version == "00" {
if let Ok(s) = encrypt00(v) {
let mut version = version.to_owned().into_bytes();
version.append(&mut s.into_bytes());
return version;
}
}
}
v.to_owned()
}
// bool: whether should store to re-encrypt when load
pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec<u8>, bool) {
if v.len() > VERSION_LEN {
let version = String::from_utf8_lossy(&v[..VERSION_LEN]);
if version == "00" {
if let Ok(v) = decrypt00(&v[VERSION_LEN..]) {
return (v, version != current_version);
}
}
}
(v.to_owned(), !v.is_empty())
}
mod test {
#[test]
fn test() {
use crate::password_security::config::*;
println!("test str");
let data = "Hello World";
let encrypted = encrypt_str_or_original(data, "00");
let (decrypted, store) = decrypt_str_or_original(&encrypted, "00");
println!("data: {}", data);
println!("encrypted: {}", encrypted);
println!("decrypted: {}", decrypted);
assert_eq!(data, decrypted);
assert_eq!("00", &encrypted[..2]);
assert_eq!(store, false);
let (_, store2) = decrypt_str_or_original(&encrypted, "01");
assert_eq!(store2, true);
println!("test vec");
let data: Vec<u8> = vec![1, 2, 3, 4];
let encrypted = encrypt_vec_or_original(&data, "00");
let (decrypted, store) = decrypt_vec_or_original(&encrypted, "00");
println!("data: {:?}", data);
println!("encrypted: {:?}", encrypted);
println!("decrypted: {:?}", decrypted);
assert_eq!(data, decrypted);
assert_eq!("00".as_bytes(), &encrypted[..2]);
assert_eq!(store, false);
let (_, store2) = decrypt_vec_or_original(&encrypted, "01");
assert_eq!(store2, true);
println!("test old");
let data = "00Hello World";
let (decrypted, store) = decrypt_str_or_original(&data, "00");
assert_eq!(data, decrypted);
assert_eq!(store, true);
let data: Vec<u8> = vec!['0' as u8, '0' as u8, 1, 2, 3, 4];
let (decrypted, store) = decrypt_vec_or_original(&data, "00");
assert_eq!(data, decrypted);
assert_eq!(store, true);
let (_, store) = decrypt_str_or_original("", "00");
assert_eq!(store, false);
let (_, store) = decrypt_vec_or_original(&vec![], "00");
assert_eq!(store, false);
}
}
}
mod base64 {
use super::symmetric_crypt;
use sodiumoxide::base64;
pub fn encrypt(v: &[u8]) -> Result<String, ()> {
if v.len() > 0 {
symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
} else {
Err(())
}
}
pub fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
if v.len() > 0 {
base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false))
} else {
Err(())
}
}
}
fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
use sodiumoxide::crypto::secretbox;
use std::convert::TryInto;
let mut keybuf = crate::get_uuid();
keybuf.resize(secretbox::KEYBYTES, 0);
let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?);
let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]);
if encrypt {
Ok(secretbox::seal(data, &nonce, &key))
} else {
secretbox::open(data, &nonce, &key)
}
}

View File

@ -537,14 +537,6 @@ pub fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe")
}
pub fn get_uuid() -> Vec<u8> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(id) = machine_uid::get() {
return id.into();
}
Config::get_key_pair().1
}
pub fn get_custom_rendezvous_server(custom: String) -> String {
if !custom.is_empty() {
return custom;

View File

@ -7,7 +7,9 @@ use hbb_common::{
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, timeout, tokio,
log,
password_security::password,
timeout, tokio,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed,
ResultType,
@ -20,6 +22,16 @@ use std::{collections::HashMap, sync::atomic::Ordering};
#[cfg(not(windows))]
use std::{fs::File, io::prelude::*};
const STR_RANDOM_PASSWORD: &'static str = "random-password";
const STR_SECURITY_PASSWORD: &'static str = "security-password";
const STR_RANDOM_PASSWORD_UPDATE_METHOD: &'static str = "random-password-update-method";
const STR_RANDOM_PASSWORD_ENABLED: &'static str = "random-password-enabled";
const STR_SECURITY_PASSWORD_ENABLED: &'static str = "security-password-enabled";
const STR_ONETIME_PASSWORD_ENABLED: &'static str = "onetime-password-enabled";
const STR_ONETIME_PASSWORD_ACTIVATED: &'static str = "onetime-password-activated";
const STR_RANDOM_PASSWORD_VALID: &'static str = "random-password-valid";
pub const STR_PASSWORD_DESCRIPTION: &'static str = "password-description";
// State with timestamp, because std::time::Instant cannot be serialized
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
#[serde(tag = "t", content = "c")]
@ -128,6 +140,7 @@ pub enum Data {
ClipboardFileEnabled(bool),
PrivacyModeState((i32, PrivacyModeState)),
TestRendezvousServer,
Bool((String, Option<bool>)),
}
#[tokio::main(flavor = "current_thread")]
@ -282,8 +295,20 @@ async fn handle(data: Data, stream: &mut Connection) {
let value;
if name == "id" {
value = Some(Config::get_id());
} else if name == "password" {
value = Some(Config::get_password());
} else if name == STR_RANDOM_PASSWORD {
value = Some(password::random_password());
} else if name == STR_SECURITY_PASSWORD {
value = Some(Config::get_security_password());
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
value = Some(password::update_method().to_string());
} else if name == STR_PASSWORD_DESCRIPTION {
value = Some(
password::random_password()
+ &password::security_enabled().to_string()
+ &password::random_enabled().to_string()
+ &password::onetime_password_enabled().to_string()
+ &password::onetime_password_activated().to_string(),
);
} else if name == "salt" {
value = Some(Config::get_salt());
} else if name == "rendezvous_server" {
@ -303,8 +328,12 @@ async fn handle(data: Data, stream: &mut Connection) {
if name == "id" {
Config::set_key_confirmed(false);
Config::set_id(&value);
} else if name == "password" {
Config::set_password(&value);
} else if name == STR_RANDOM_PASSWORD {
password::set_random_password(&value);
} else if name == STR_SECURITY_PASSWORD {
Config::set_security_password(&value);
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
password::set_update_method(&value);
} else if name == "salt" {
Config::set_salt(&value);
} else {
@ -344,6 +373,36 @@ async fn handle(data: Data, stream: &mut Connection) {
Data::TestRendezvousServer => {
crate::test_rendezvous_server();
}
Data::Bool((name, value)) => match value {
None => {
let value;
if name == STR_SECURITY_PASSWORD_ENABLED {
value = Some(password::security_enabled());
} else if name == STR_RANDOM_PASSWORD_ENABLED {
value = Some(password::random_enabled());
} else if name == STR_ONETIME_PASSWORD_ENABLED {
value = Some(password::onetime_password_enabled());
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
value = Some(password::onetime_password_activated());
} else if name == STR_RANDOM_PASSWORD_VALID {
value = Some(password::random_password_valid());
} else {
return;
}
allow_err!(stream.send(&Data::Bool((name, value))).await);
}
Some(value) => {
if name == STR_SECURITY_PASSWORD_ENABLED {
password::set_security_enabled(value);
} else if name == STR_RANDOM_PASSWORD_ENABLED {
password::set_random_enabled(value);
} else if name == STR_ONETIME_PASSWORD_ENABLED {
password::set_onetime_password_enabled(value);
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
password::set_onetime_password_activated(value);
}
}
},
_ => {}
}
}
@ -426,6 +485,10 @@ where
.await
}
async fn send_bool(&mut self, name: &str, value: bool) -> ResultType<()> {
self.send(&Data::Bool((name.to_owned(), Some(value)))).await
}
pub async fn next_timeout(&mut self, ms_timeout: u64) -> ResultType<Option<Data>> {
Ok(timeout(ms_timeout, self.next()).await??)
}
@ -497,9 +560,128 @@ pub async fn set_config(name: &str, value: String) -> ResultType<()> {
set_config_async(name, value).await
}
pub fn set_password(v: String) -> ResultType<()> {
Config::set_password(&v);
set_config("password", v)
#[tokio::main(flavor = "current_thread")]
async fn get_bool(name: &str) -> ResultType<Option<bool>> {
get_bool_async(name, 1_000).await
}
async fn get_bool_async(name: &str, ms_timeout: u64) -> ResultType<Option<bool>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Bool((name.to_owned(), None))).await?;
if let Some(Data::Bool((name2, value))) = c.next_timeout(ms_timeout).await? {
if name == name2 {
return Ok(value);
}
}
return Ok(None);
}
pub async fn set_bool_async(name: &str, value: bool) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send_bool(name, value).await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_bool(name: &str, value: bool) -> ResultType<()> {
set_bool_async(name, value).await
}
pub fn get_random_password() -> String {
if let Ok(Some(password)) = get_config(STR_RANDOM_PASSWORD) {
password::set_random_password(&password);
password
} else {
password::random_password()
}
}
pub fn set_random_password(v: String) -> ResultType<()> {
password::set_random_password(&v);
set_config(STR_RANDOM_PASSWORD, v)
}
pub fn set_security_password(v: String) -> ResultType<()> {
Config::set_security_password(&v);
set_config(STR_SECURITY_PASSWORD, v)
}
pub fn random_password_update_method() -> String {
if let Ok(Some(method)) = get_config(STR_RANDOM_PASSWORD_UPDATE_METHOD) {
password::set_update_method(&method);
method
} else {
password::update_method()
}
}
pub fn set_random_password_update_method(method: String) -> ResultType<()> {
password::set_update_method(&method);
set_config(STR_RANDOM_PASSWORD_UPDATE_METHOD, method)
}
pub fn is_random_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_RANDOM_PASSWORD_ENABLED) {
password::set_random_enabled(enabled);
enabled
} else {
password::random_enabled()
}
}
pub fn set_random_password_enabled(enabled: bool) -> ResultType<()> {
password::set_random_enabled(enabled);
set_bool(STR_RANDOM_PASSWORD_ENABLED, enabled)
}
pub fn is_security_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_SECURITY_PASSWORD_ENABLED) {
password::set_security_enabled(enabled);
enabled
} else {
password::security_enabled()
}
}
pub fn set_security_password_enabled(enabled: bool) -> ResultType<()> {
password::set_security_enabled(enabled);
set_bool(STR_SECURITY_PASSWORD_ENABLED, enabled)
}
pub fn is_onetime_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_ONETIME_PASSWORD_ENABLED) {
password::set_onetime_password_enabled(enabled);
enabled
} else {
password::onetime_password_enabled()
}
}
pub fn set_onetime_password_enabled(enabled: bool) -> ResultType<()> {
password::set_onetime_password_enabled(enabled);
set_bool(STR_ONETIME_PASSWORD_ENABLED, enabled)
}
pub fn is_onetime_password_activated() -> bool {
if let Ok(Some(activated)) = get_bool(STR_ONETIME_PASSWORD_ACTIVATED) {
password::set_onetime_password_activated(activated);
activated
} else {
password::onetime_password_activated()
}
}
pub fn set_onetime_password_activated(activated: bool) -> ResultType<()> {
password::set_onetime_password_activated(activated);
set_bool(STR_ONETIME_PASSWORD_ACTIVATED, activated)
}
pub fn is_random_password_valid() -> bool {
if let Ok(Some(valid)) = get_bool(STR_RANDOM_PASSWORD_VALID) {
valid
} else {
password::random_password_valid()
}
}
pub fn get_id() -> String {
@ -518,15 +700,6 @@ pub fn get_id() -> String {
}
}
pub fn get_password() -> String {
if let Ok(Some(v)) = get_config("password") {
Config::set_password(&v);
v
} else {
Config::get_password()
}
}
pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>) {
if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await {
let mut urls = v.split(",");

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持RustDesk后台服务"),
("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能请在接下来的RustDesk应用设置页面中找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Random Password After Session", "会话结束更新随机密码"),
("Keep", "保持"),
("Update", "更新"),
("Disable", "禁用"),
("Onetime Password", "一次性口令"),
("Verification Method", "密码验证方式"),
("Enable security password", "启用安全密码"),
("Enable random password", "启用随机密码"),
("Enable onetime password", "启用一次性访问功能"),
("Disable onetime password", "禁用一次性访问功能"),
("Activate onetime password", "激活一次性访问功能"),
("Set security password", "设置安全密码"),
("Connection not allowed", "对方不允许连接"),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持RustDesk後台服務"),
("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Random Password After Session", "會話結束更新隨機密碼"),
("Keep", "保持"),
("Update", "更新"),
("Disable", "禁用"),
("Onetime Password", "一次性口令"),
("Verification Method", "密碼驗證方式"),
("Enable security password", "啟用安全密碼"),
("Enable random password", "啟用隨機密碼"),
("Enable onetime password", "啟用一次性訪問功能"),
("Disable onetime password", "禁用一次性訪問功能"),
("Activate onetime password", "激活一次性訪問功能"),
("Set security password", "設置安全密碼"),
("Connection not allowed", "對方不允許連接"),
].iter().cloned().collect();
}

View File

@ -148,7 +148,7 @@ fn main() {
return;
} else if args[0] == "--password" {
if args.len() == 2 {
ipc::set_password(args[1].to_owned()).unwrap();
ipc::set_security_password(args[1].to_owned()).unwrap();
}
return;
} else if args[0] == "--check-hwcodec-config" {

View File

@ -389,7 +389,7 @@ impl RendezvousMediator {
async fn register_pk(&mut self, socket: &mut FramedSocket) -> ResultType<()> {
let mut msg_out = Message::new();
let pk = Config::get_key_pair().1;
let uuid = crate::get_uuid();
let uuid = hbb_common::get_uuid();
let id = Config::get_id();
self.last_id_pk_registry = id.clone();
msg_out.set_register_pk(RegisterPk {

View File

@ -8,6 +8,7 @@ use crate::video_service;
use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
use crate::{ipc, VERSION};
use hbb_common::fs::can_enable_overwrite_detection;
use hbb_common::password_security::password;
use hbb_common::{
config::Config,
fs,
@ -398,6 +399,7 @@ impl Connection {
video_service::notify_video_frame_feched(id, None);
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
video_service::VIDEO_QOS.lock().unwrap().reset();
password::after_session(conn.authorized);
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
conn.on_close(&err.to_string(), false);
}
@ -571,7 +573,7 @@ impl Connection {
let url = self.api_server.clone();
let mut v = v;
v["id"] = json!(Config::get_id());
v["uuid"] = json!(base64::encode(crate::get_uuid()));
v["uuid"] = json!(base64::encode(hbb_common::get_uuid()));
v["Id"] = json!(self.inner.id);
tokio::spawn(async move {
allow_err!(Self::post_audit_async(url, v).await);
@ -778,6 +780,36 @@ impl Connection {
self.tx_input.send(MessageInput::Key((msg, press))).ok();
}
fn validate_password(&mut self, lr_password: Vec<u8>) -> bool {
let validate = |password: String| {
if password.len() == 0 {
return false;
}
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&self.hash.salt);
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
hasher2.finalize()[..] == lr_password[..]
};
if password::security_enabled() {
if validate(Config::get_security_password()) {
return true;
}
}
if password::random_password_valid() {
if validate(password::random_password()) {
if password::onetime_password_activated() {
password::set_onetime_password_activated(false);
}
return true;
}
}
false
}
async fn on_message(&mut self, msg: Message) -> bool {
if let Some(message::Union::LoginRequest(lr)) = msg.union {
if let Some(o) = lr.option.as_ref() {
@ -853,12 +885,10 @@ impl Connection {
} else if lr.password.is_empty() {
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
let mut hasher = Sha256::new();
hasher.update(&Config::get_password());
hasher.update(&self.hash.salt);
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
if password::passwords().len() == 0 {
self.send_login_error("Connection not allowed").await;
return false;
}
let mut failure = LOGIN_FAILURES
.lock()
.unwrap()
@ -871,7 +901,7 @@ impl Connection {
.await;
} else if time == failure.0 && failure.1 > 6 {
self.send_login_error("Please try 1 minute later").await;
} else if hasher2.finalize()[..] != lr.password[..] {
} else if !self.validate_password(lr.password.clone()) {
if failure.0 == time {
failure.1 += 1;
failure.2 += 1;

View File

@ -43,6 +43,7 @@ struct UI(
Arc<Mutex<HashMap<String, String>>>,
Arc<Mutex<String>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
);
struct UIHostHandler;
@ -169,7 +170,7 @@ pub fn start(args: &mut [String]) {
impl UI {
fn new(childs: Childs) -> Self {
let res = check_connect_status(true);
Self(childs, res.0, res.1, Default::default(), res.2)
Self(childs, res.0, res.1, Default::default(), res.2, res.3)
}
fn recent_sessions_updated(&mut self) -> bool {
@ -186,16 +187,16 @@ impl UI {
ipc::get_id()
}
fn get_password(&mut self) -> String {
ipc::get_password()
fn get_random_password(&self) -> String {
ipc::get_random_password()
}
fn update_password(&mut self, password: String) {
if password.is_empty() {
allow_err!(ipc::set_password(Config::get_auto_password()));
} else {
allow_err!(ipc::set_password(password));
}
fn update_random_password(&self) {
allow_err!(ipc::set_random_password(Config::get_auto_password()));
}
fn set_security_password(&self, password: String) {
allow_err!(ipc::set_security_password(password));
}
fn get_remote_id(&mut self) -> String {
@ -704,7 +705,7 @@ impl UI {
}
fn get_uuid(&self) -> String {
base64::encode(crate::get_uuid())
base64::encode(hbb_common::get_uuid())
}
fn open_url(&self, url: String) {
@ -774,6 +775,54 @@ impl UI {
fn get_langs(&self) -> String {
crate::lang::LANGS.to_string()
}
fn random_password_update_method(&self) -> String {
ipc::random_password_update_method()
}
fn set_random_password_update_method(&self, method: String) {
allow_err!(ipc::set_random_password_update_method(method));
}
fn is_random_password_enabled(&self) -> bool {
ipc::is_random_password_enabled()
}
fn set_random_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_random_password_enabled(enabled));
}
fn is_security_password_enabled(&self) -> bool {
ipc::is_security_password_enabled()
}
fn set_security_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_security_password_enabled(enabled));
}
fn is_onetime_password_enabled(&self) -> bool {
ipc::is_onetime_password_enabled()
}
fn set_onetime_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_onetime_password_enabled(enabled));
}
fn is_onetime_password_activated(&self) -> bool {
ipc::is_onetime_password_activated()
}
fn set_onetime_password_activated(&self, activated: bool) {
allow_err!(ipc::set_onetime_password_activated(activated));
}
fn is_random_password_valid(&self) -> bool {
ipc::is_random_password_valid()
}
fn password_description(&mut self) -> String {
self.5.lock().unwrap().clone()
}
}
impl sciter::EventHandler for UI {
@ -783,8 +832,9 @@ impl sciter::EventHandler for UI {
fn is_xfce();
fn using_public_server();
fn get_id();
fn get_password();
fn update_password(String);
fn get_random_password();
fn update_random_password();
fn set_security_password(String);
fn get_remote_id();
fn set_remote_id(String);
fn closing(i32, i32, i32, i32);
@ -854,6 +904,18 @@ impl sciter::EventHandler for UI {
fn get_uuid();
fn has_hwcodec();
fn get_langs();
fn random_password_update_method();
fn set_random_password_update_method(String);
fn is_random_password_enabled();
fn set_random_password_enabled(bool);
fn is_security_password_enabled();
fn set_security_password_enabled(bool);
fn is_onetime_password_enabled();
fn set_onetime_password_enabled(bool);
fn is_onetime_password_activated();
fn set_onetime_password_activated(bool);
fn is_random_password_valid();
fn password_description();
}
}
@ -893,6 +955,7 @@ async fn check_connect_status_(
status: Arc<Mutex<Status>>,
options: Arc<Mutex<HashMap<String, String>>>,
rx: mpsc::UnboundedReceiver<ipc::Data>,
password: Arc<Mutex<String>>,
) {
let mut key_confirmed = false;
let mut rx = rx;
@ -919,6 +982,8 @@ async fn check_connect_status_(
Ok(Some(ipc::Data::Config((name, Some(value))))) => {
if name == "id" {
id = value;
} else if name == ipc::STR_PASSWORD_DESCRIPTION {
*password.lock().unwrap() = value;
}
}
Ok(Some(ipc::Data::OnlineStatus(Some((mut x, c))))) => {
@ -938,6 +1003,7 @@ async fn check_connect_status_(
c.send(&ipc::Data::OnlineStatus(None)).await.ok();
c.send(&ipc::Data::Options(None)).await.ok();
c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok();
c.send(&ipc::Data::Config((ipc::STR_PASSWORD_DESCRIPTION.to_owned(), None))).await.ok();
}
}
}
@ -986,14 +1052,19 @@ fn check_connect_status(
Arc<Mutex<Status>>,
Arc<Mutex<HashMap<String, String>>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
) {
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
let options = Arc::new(Mutex::new(Config::get_options()));
let cloned = status.clone();
let cloned_options = options.clone();
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
std::thread::spawn(move || check_connect_status_(reconnect, cloned, cloned_options, rx));
(status, options, tx)
let password = Arc::new(Mutex::new(String::default()));
let cloned_password = password.clone();
std::thread::spawn(move || {
check_connect_status_(reconnect, cloned, cloned_options, rx, cloned_password)
});
(status, options, tx, password)
}
const INVALID_FORMAT: &'static str = "Invalid format";

View File

@ -120,7 +120,7 @@ textarea:empty {
@ELLIPSIS;
}
div.password svg {
div.password svg:not(.checkmark) {
padding-left: 1em;
size: 16px;
color: #ddd;

View File

@ -141,7 +141,7 @@ function adjustBorder() {
if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN);
}
var svg_checkmark = <svg viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
var svg_checkmark = <svg class="checkmark" viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
var svg_edit = <svg #edit viewBox="0 0 384 384">
<path d="M0 304v80h80l236-236-80-80zM378 56L328 6c-8-8-22-8-30 0l-39 39 80 80 39-39c8-8 8-22 0-30z"/>
</svg>;

View File

@ -403,3 +403,18 @@ div.remote-session svg#menu {
background: none;
color: white;
}
svg#refresh-password {
display: inline-block;
stroke:#ddd;
}
svg#refresh-password:hover {
stroke:color(text);
}
li:disabled, li:disabled:hover {
color: color(lighter-text);
background: color(menu);
}

View File

@ -20,6 +20,7 @@ var svg_menu = <svg #menu viewBox="0 0 512 512">
<circle cx="256" cy="448" r="64"/>
<circle cx="256" cy="64" r="64"/>
</svg>;
var svg_refresh_password = <svg #refresh-password xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 2v6h6M2.66 15.57a10 10 0 1 0 .57-8.38"/></svg>;
var my_id = "";
function get_id() {
@ -520,10 +521,6 @@ class App: Reactor.Component
var is_can_screen_recording = handler.is_can_screen_recording(false);
return
<div .app>
<popup><menu.context #edit-password-context>
<li #refresh-password>{translate('Refresh random password')}</li>
<li #set-password>{translate('Set your own password')}</li>
</menu></popup>
<div .left-pane>
<div>
<div .title>{translate('Your Desktop')}</div>
@ -533,8 +530,7 @@ class App: Reactor.Component
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
</div>
<div .your-desktop>
<div>{translate('Password')}</div>
<Password />
<PasswordArea />
</div>
</div>
{!is_win || handler.is_installed() ? "": <InstallMe />}
@ -806,44 +802,151 @@ function watch_screen_recording() {
class PasswordEyeArea : Reactor.Component {
render() {
var show = handler.is_random_password_valid();
var value = show ? handler.get_random_password() : "-";
return
<div .eye-area style="width: *">
<input|text @{this.input} readonly value="******" />
{svg_eye}
<input|text @{this.input} readonly value={value} />
{svg_refresh_password}
</div>;
}
event mouseenter {
var me = this;
me.leaved = false;
me.timer(300ms, function() {
if (me.leaved) return;
me.input.value = handler.get_password();
});
}
event mouseleave {
this.leaved = true;
this.input.value = "******";
event click $(svg#refresh-password) (_, me) {
if (handler.is_random_password_valid()) handler.update_random_password();
this.update();
}
}
class Password: Reactor.Component {
var verificationMethodMenu;
class VerificationMethodMenu: Reactor.Component {
function this() {
verificationMethodMenu = this;
}
function render() {
return <div .password style="flow:horizontal">
<PasswordEyeArea />
{svg_edit}
if (!this.show) return <li />;
var me = this;
self.timer(1ms, function() { me.toggleMenuState() });
return <li>{translate('Verification Method')}
<menu #verification-method>
<li #verification-method-security><span>{svg_checkmark}</span>{translate('Enable security password')}</li>
<li #verification-method-random><span>{svg_checkmark}</span>{translate('Enable random password')}</li>
</menu>
</li>;
}
function toggleMenuState() {
var security_enabled = handler.is_security_password_enabled();
var random_enabled = handler.is_random_password_enabled();
var onetime_enabled = handler.is_onetime_password_enabled();
for (var (index, el) in this.$$(menu#verification-method>li)) {
if (index == 0) el.attributes.toggleClass("selected", security_enabled);
if (index == 1) el.attributes.toggleClass("selected", random_enabled);
}
}
event click $(menu#verification-method>li) (_, me) {
switch (me.id.substring('verification-method-'.length)) {
case 'security':
{
var security_enabled = handler.is_security_password_enabled();
handler.set_security_password_enabled(!security_enabled);
}
break;
case 'random':
{
var random_enabled = handler.is_random_password_enabled();
handler.set_random_password_enabled(!random_enabled);
}
break;
}
this.toggleMenuState();
passwordArea.update();
}
}
var randomPasswordUpdateMethodMenu;
class RandomPasswordUpdateMethodMenu: Reactor.Component {
function this() {
randomPasswordUpdateMethodMenu = this;
}
function render() {
if (!this.show) return <li />;
var me = this;
var random_enabled = handler.is_random_password_enabled();
self.timer(1ms, function() { me.toggleMenuState() });
return <li disabled={ random_enabled ? "false" : "true" }>{translate('Random Password After Session')}
<menu #random-password-update-method>
<li #random-password-update-method-keep><span>{svg_checkmark}</span>{translate('Keep')}</li>
<li #random-password-update-method-update><span>{svg_checkmark}</span>{translate('Update')}</li>
<li #random-password-update-method-disable><span>{svg_checkmark}</span>{translate('Disable')}</li>
</menu>
</li>;
}
function toggleMenuState() {
var method = handler.random_password_update_method();
for (var (index, el) in this.$$(menu#random-password-update-method>li)) {
if (index == 0) el.attributes.toggleClass("selected", method == "KEEP");
if (index == 1) el.attributes.toggleClass("selected", method == "UPDATE");
if (index == 2) el.attributes.toggleClass("selected", method == "DISABLE");
}
}
event click $(menu#random-password-update-method>li) (_, me) {
if (me.id === 'random-password-update-method-keep') handler.set_random_password_update_method("KEEP");
if (me.id === 'random-password-update-method-update') handler.set_random_password_update_method("UPDATE");
if (me.id === 'random-password-update-method-disable') handler.set_random_password_update_method("DISABLE");
this.toggleMenuState();
passwordArea.update();
}
}
var passwordArea;
class PasswordArea: Reactor.Component {
function this() {
passwordArea = this;
}
function render() {
var onetime_enabled = handler.is_onetime_password_enabled();
return
<div>
<div>{translate(onetime_enabled ? 'Onetime Password' : 'Password')}</div>
<div .password style="flow:horizontal">
{this.renderPop()}
<PasswordEyeArea />
{svg_edit}
</div>
</div>;
}
event click $(svg#edit) (_, me) {
var menu = $(menu#edit-password-context);
me.popup(menu);
function renderPop() {
var security_enabled = handler.is_security_password_enabled();
var random_enabled = handler.is_random_password_enabled();
var onetime_enabled = handler.is_onetime_password_enabled();
var onetime_activated = handler.is_onetime_password_activated();
return <popup><menu.context #edit-password-context>
<li #enable-onetime-password disabled={ random_enabled ? "false" : "true" }>{translate(onetime_enabled ? "Disable onetime password" : "Enable onetime password")}</li>
<li #activate-onetime-password disabled={ !random_enabled || !onetime_enabled || onetime_activated ? "true" : "false" }>{translate('Activate onetime password')}</li>
<div .separator />
<VerificationMethodMenu />
<div .separator />
<li #set-password disabled={ security_enabled ? "false" : "true" }>{translate('Set security password')}</li>
<div .separator />
<RandomPasswordUpdateMethodMenu />
</menu></popup>;
}
event click $(li#refresh-password) {
handler.update_password("");
this.update();
event click $(svg#edit) (_, me) {
randomPasswordUpdateMethodMenu.update({show: true });
verificationMethodMenu.update({show: true });
var menu = $(menu#edit-password-context);
me.popup(menu);
}
event click $(li#set-password) {
@ -862,12 +965,36 @@ class Password: Reactor.Component {
if (p0 != p1) {
return translate("The confirmation is not identical.");
}
handler.update_password(p0);
handler.set_security_password(p0);
me.update();
});
}
event click $(li#enable-onetime-password) {
var onetime_enabled = handler.is_onetime_password_enabled();
handler.set_onetime_password_enabled(!onetime_enabled);
passwordArea.update();
}
event click $(li#activate-onetime-password) {
handler.set_onetime_password_activated(true);
passwordArea.update();
}
}
var last_password_description = "";
function updatePasswordArea() {
self.timer(1s, function() {
var description = handler.password_description();
if (last_password_description != description) {
last_password_description = description
passwordArea.update();
}
updatePasswordArea();
});
}
updatePasswordArea();
class ID: Reactor.Component {
function render() {
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="21"