opt password sync, opt ab widgets (#7582)

* Opt sync conctrl with password source, add some comments
* For sync from recent, legacy ab remove forceRelay, rdpPort, rdpUsername,
  because it's not used, personal ab add sync hash
* Opt style of add Id dialog

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2024-04-02 22:08:47 +08:00 committed by GitHub
parent 74af7ef8b2
commit d7b47b49d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 369 additions and 243 deletions

View File

@ -87,7 +87,10 @@ class _AddressBookState extends State<AddressBook> {
child: Column(
children: [
_buildAbDropdown(),
_buildTagHeader().marginOnly(left: 8.0, right: 0),
_buildTagHeader().marginOnly(
left: 8.0,
right: gFFI.abModel.legacyMode.value ? 8.0 : 0,
top: gFFI.abModel.legacyMode.value ? 8.0 : 0),
Expanded(
child: Container(
width: double.infinity,
@ -415,6 +418,7 @@ class _AddressBookState extends State<AddressBook> {
return;
}
var isInProgress = false;
var passwordVisible = false;
IDTextEditingController idController = IDTextEditingController(text: '');
TextEditingController aliasController = TextEditingController(text: '');
TextEditingController passwordController = TextEditingController(text: '');
@ -460,6 +464,24 @@ class _AddressBookState extends State<AddressBook> {
}
double marginBottom = 4;
row({required Widget lable, required Widget input}) {
return Row(
children: [
!isMobile
? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: lable.marginOnly(right: 10))
: SizedBox.shrink(),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200),
child: input),
),
],
).marginOnly(bottom: !isMobile ? 8 : 0);
}
return CustomAlertDialog(
title: Text(translate("Add ID")),
content: Column(
@ -467,75 +489,90 @@ class _AddressBookState extends State<AddressBook> {
children: [
Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Row(
children: [
Text(
'*',
style: TextStyle(color: Colors.red, fontSize: 14),
),
Text(
'ID',
style: style,
),
],
),
).marginOnly(bottom: marginBottom),
TextField(
controller: idController,
inputFormatters: [IDTextInputFormatter()],
decoration:
InputDecoration(errorText: errorMsg, errorMaxLines: 5),
),
Align(
alignment: Alignment.centerLeft,
child: Text(
row(
lable: Row(
children: [
Text(
'*',
style: TextStyle(color: Colors.red, fontSize: 14),
),
Text(
'ID',
style: style,
),
],
),
input: TextField(
controller: idController,
inputFormatters: [IDTextInputFormatter()],
decoration: InputDecoration(
labelText: !isMobile ? null : translate('ID'),
errorText: errorMsg,
errorMaxLines: 5),
)),
row(
lable: Text(
translate('Alias'),
style: style,
),
).marginOnly(top: 8, bottom: marginBottom),
TextField(
controller: aliasController,
input: TextField(
controller: aliasController,
decoration: InputDecoration(
labelText: !isMobile ? null : translate('Alias'),
)),
),
if (isCurrentAbShared)
row(
lable: Text(
translate('Password'),
style: style,
),
input: TextField(
controller: passwordController,
obscureText: !passwordVisible,
decoration: InputDecoration(
labelText: !isMobile ? null : translate('Password'),
suffixIcon: IconButton(
icon: Icon(
passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: MyTheme.lightTheme.primaryColor),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
)),
if (gFFI.abModel.currentAbTags.isNotEmpty)
Align(
alignment: Alignment.centerLeft,
child: Text(
translate('Password'),
translate('Tags'),
style: style,
),
).marginOnly(top: 8, bottom: marginBottom),
if (isCurrentAbShared)
TextField(
controller: passwordController,
obscureText: true,
if (gFFI.abModel.currentAbTags.isNotEmpty)
Align(
alignment: Alignment.centerLeft,
child: Wrap(
children: tags
.map((e) => AddressBookTag(
name: e,
tags: selectedTag,
onTap: () {
if (selectedTag.contains(e)) {
selectedTag.remove(e);
} else {
selectedTag.add(e);
}
},
showActionMenu: false))
.toList(growable: false),
),
),
Align(
alignment: Alignment.centerLeft,
child: Text(
translate('Tags'),
style: style,
),
).marginOnly(top: 8, bottom: marginBottom),
Align(
alignment: Alignment.centerLeft,
child: Wrap(
children: tags
.map((e) => AddressBookTag(
name: e,
tags: selectedTag,
onTap: () {
if (selectedTag.contains(e)) {
selectedTag.remove(e);
} else {
selectedTag.add(e);
}
},
showActionMenu: false))
.toList(growable: false),
),
),
],
),
const SizedBox(

View File

@ -1918,11 +1918,9 @@ void addPeersToAbDialog(
Future<bool> addTo(String abname) async {
final mapList = peers.map((e) {
var json = e.toJson();
// remove shared password when add to other address book
// remove password when add to another address book to avoid re-share
json.remove('password');
if (gFFI.abModel.addressbooks[abname]?.isPersonal() != true) {
json.remove('hash');
}
json.remove('hash');
return json;
}).toList();
final errMsg = await gFFI.abModel.addPeersTo(mapList, abname);
@ -1986,6 +1984,7 @@ void addPeersToAbDialog(
content: Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// https://github.com/flutter/flutter/issues/145081
DropdownMenu(
initialSelection: currentName.value,
onSelected: (value) {
@ -2026,18 +2025,23 @@ void addPeersToAbDialog(
}
void setSharedAbPasswordDialog(String abName, Peer peer) {
TextEditingController controller = TextEditingController(text: peer.password);
TextEditingController controller = TextEditingController(text: '');
RxBool isInProgress = false.obs;
RxBool isInputEmpty = true.obs;
bool passwordVisible = false;
controller.addListener(() {
isInputEmpty.value = controller.text.isEmpty;
});
gFFI.dialogManager.show((setState, close, context) {
submit() async {
change(String password) async {
isInProgress.value = true;
bool res = await gFFI.abModel
.changeSharedPassword(abName, peer.id, controller.text);
close();
bool res =
await gFFI.abModel.changeSharedPassword(abName, peer.id, password);
isInProgress.value = false;
if (res) {
showToast(translate('Successful'));
}
close();
}
cancel() {
@ -2049,22 +2053,38 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.key, color: MyTheme.accent),
Text(translate('Set shared password')).paddingOnly(left: 10),
Text(translate(peer.password.isEmpty
? 'Set shared password'
: 'Change Password'))
.paddingOnly(left: 10),
],
),
content: Obx(() => Column(children: [
TextField(
controller: controller,
obscureText: true,
autofocus: true,
obscureText: !passwordVisible,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility : Icons.visibility_off,
color: MyTheme.lightTheme.primaryColor),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
),
Row(children: [
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
Text(
translate('share_warning_tip'),
style: TextStyle(fontSize: 12),
)
]).marginSymmetric(vertical: 10),
if (!gFFI.abModel.current.isPersonal())
Row(children: [
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
Text(
translate('share_warning_tip'),
style: TextStyle(fontSize: 12),
)
]).marginSymmetric(vertical: 10),
// NOT use Offstage to wrap LinearProgressIndicator
isInProgress.value ? const LinearProgressIndicator() : Offstage()
])),
@ -2075,13 +2095,22 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
onPressed: cancel,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
if (peer.password.isNotEmpty)
dialogButton(
"Remove",
icon: Icon(Icons.delete_outline_rounded),
onPressed: () => change(''),
buttonStyle: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Colors.red)),
),
Obx(() => dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed:
isInputEmpty.value ? null : () => change(controller.text),
)),
],
onSubmit: submit,
onSubmit: isInputEmpty.value ? null : () => change(controller.text),
onCancel: cancel,
);
});

View File

@ -139,21 +139,30 @@ class _PeerCardState extends State<_PeerCard>
mainAxisSize: MainAxisSize.max,
children: [
Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: isMobile
? BorderRadius.circular(_tileRadius)
: BorderRadius.only(
topLeft: Radius.circular(_tileRadius),
bottomLeft: Radius.circular(_tileRadius),
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: isMobile
? BorderRadius.circular(_tileRadius)
: BorderRadius.only(
topLeft: Radius.circular(_tileRadius),
bottomLeft: Radius.circular(_tileRadius),
),
),
alignment: Alignment.center,
width: isMobile ? 50 : 42,
height: isMobile ? 50 : null,
child: Stack(
children: [
getPlatformImage(peer.platform, size: isMobile ? 38 : 30)
.paddingAll(6),
if (_shouldBuildPasswordIcon(peer))
Positioned(
top: 1,
left: 1,
child: Icon(Icons.key, size: 6, color: Colors.white),
),
),
alignment: Alignment.center,
width: isMobile ? 50 : 42,
height: isMobile ? 50 : null,
child: getPlatformImage(peer.platform, size: isMobile ? 38 : 30)
.paddingAll(6),
),
],
)),
Expanded(
child: Container(
decoration: BoxDecoration(
@ -216,12 +225,6 @@ class _PeerCardState extends State<_PeerCard>
child: child,
),
),
if (_shouldBuildPasswordIcon(peer))
Positioned(
top: 2,
left: isMobile ? 60 : 50,
child: Icon(Icons.key, size: 12),
),
if (colors.isNotEmpty)
Positioned(
top: 2,
@ -329,7 +332,7 @@ class _PeerCardState extends State<_PeerCard>
Positioned(
top: 4,
left: 12,
child: Icon(Icons.key, size: 12),
child: Icon(Icons.key, size: 12, color: Colors.white),
),
if (colors.isNotEmpty)
Positioned(
@ -1102,7 +1105,8 @@ class AddressBookPeerCard extends BasePeerCard {
MenuEntryBase<String> _changeSharedAbPassword() {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Set shared password'),
translate(
peer.password.isEmpty ? 'Set shared password' : 'Change Password'),
style: style,
),
proc: () {

View File

@ -320,7 +320,7 @@ class AbModel {
peer['password'] = password;
}
final ret = await addPeersTo([peer], _currentName.value);
_timerCounter = 0;
_syncAllFromRecent = true;
return ret;
}
@ -364,7 +364,7 @@ class AbModel {
final personalAb = addressbooks[_personalAddressBookName];
if (personalAb != null) {
ret = await personalAb.changePersonalHashPassword(id, hash);
await pullNonLegacyAfterChange();
await personalAb.pullAb(quiet: true);
} else {
final legacyAb = addressbooks[_legacyAddressBookName];
if (legacyAb != null) {
@ -377,9 +377,10 @@ class AbModel {
Future<bool> changeSharedPassword(
String abName, String id, String password) async {
final ret =
await addressbooks[abName]?.changeSharedPassword(id, password) ?? false;
await pullNonLegacyAfterChange();
final ab = addressbooks[abName];
if (ab == null) return false;
final ret = await ab.changeSharedPassword(id, password);
await ab.pullAb(quiet: true);
return ret;
}
@ -538,9 +539,7 @@ class AbModel {
"name": key,
"tags": value.tags,
"peers": value.peers
.map((e) => value.isPersonal()
? e.toPersonalAbUploadJson(true)
: e.toSharedAbCacheJson())
.map((e) => e.toCustomJson(includingHash: value.isPersonal()))
.toList(),
"tag_colors": jsonEncode(value.tagColors)
});
@ -745,6 +744,10 @@ abstract class BaseAb {
name() == _legacyAddressBookName;
}
bool isLegacy() {
return name() == _legacyAddressBookName;
}
Future<void> pullAb({quiet = false}) async {
debugPrint("pull ab \"${name()}\"");
if (abLoading.value) return;
@ -1049,9 +1052,6 @@ class LegacyAb extends BaseAb {
p.hostname = r.hostname.isEmpty ? p.hostname : r.hostname;
p.platform = r.platform.isEmpty ? p.platform : r.platform;
p.alias = p.alias.isEmpty ? r.alias : p.alias;
p.forceAlwaysRelay = r.forceAlwaysRelay;
p.rdpPort = r.rdpPort;
p.rdpUsername = r.rdpUsername;
}
@override
@ -1151,7 +1151,7 @@ class LegacyAb extends BaseAb {
Map<String, dynamic> _serialize() {
final peersJsonData =
peers.map((e) => e.toPersonalAbUploadJson(true)).toList();
peers.map((e) => e.toCustomJson(includingHash: true)).toList();
for (var e in tags) {
if (tagColors[e] == null) {
tagColors[e] = str2color2(e, existing: tagColors.values.toList()).value;
@ -1491,38 +1491,55 @@ class Ab extends BaseAb {
Future<bool> changePersonalHashPassword(String id, String hash) async {
if (!personal) return false;
if (!peers.any((e) => e.id == id)) return false;
return _setPassword({"id": id, "hash": hash});
return await _setPassword({"id": id, "hash": hash});
}
@override
Future<bool> changeSharedPassword(String id, String password) async {
if (personal) return false;
return _setPassword({"id": id, "password": password});
return await _setPassword({"id": id, "password": password});
}
@override
Future<void> syncFromRecent(List<Peer> recents) async {
bool uiUpdate = false;
bool peerSyncEqual(Peer a, Peer b) {
return a.username == b.username &&
a.platform == b.platform &&
a.hostname == b.hostname;
}
bool saveCache = false;
final api =
"${await bind.mainGetApiServer()}/api/ab/peer/update/${profile.guid}";
var headers = getHttpHeaders();
headers['Content-Type'] = "application/json";
Future<bool> syncOnePeer(Peer p, Peer r) async {
p.username = r.username;
p.hostname = r.hostname;
p.platform = r.platform;
final api =
"${await bind.mainGetApiServer()}/api/ab/peer/update/${profile.guid}";
var headers = getHttpHeaders();
headers['Content-Type'] = "application/json";
final body = jsonEncode({
"id": p.id,
"username": r.username,
"hostname": r.hostname,
"platform": r.platform
});
Future<bool> trySyncOnePeer(Peer p, Peer r) async {
var map = Map<String, String>.fromEntries([]);
if (p.sameServer != true &&
r.username.isNotEmpty &&
p.username != r.username) {
p.username = r.username;
map['username'] = r.username;
}
if (p.sameServer != true &&
r.hostname.isNotEmpty &&
p.hostname != r.hostname) {
p.hostname = r.hostname;
map['hostname'] = r.hostname;
}
if (p.sameServer != true &&
r.platform.isNotEmpty &&
p.platform != r.platform) {
p.platform = r.platform;
map['platform'] = r.platform;
}
if (personal && r.hash.isNotEmpty && p.hash != r.hash) {
p.hash = r.hash;
map['hash'] = r.hash;
saveCache = true;
}
if (map.isEmpty) {
// no need to sync
return false;
}
map['id'] = p.id;
final body = jsonEncode(map);
final resp = await http.put(Uri.parse(api), headers: headers, body: body);
final errMsg = _jsonDecodeActionResp(resp);
if (errMsg.isNotEmpty) {
@ -1534,35 +1551,20 @@ class Ab extends BaseAb {
}
try {
/* Remove this because IDs that are not on the server can't be synced, then sync will happen every startup.
// Try add new peers to personal ab
if (personal) {
for (var r in recents) {
if (peers.length < gFFI.abModel._maxPeerOneAb) {
if (!peers.any((e) => e.id == r.id)) {
var err = await addPeers([r.toPersonalAbUploadJson(true)]);
if (err == null) {
peers.add(r);
uiUpdate = true;
}
}
}
}
}
*/
final syncPeers = peers.where((p0) => p0.sameServer != true);
for (var p in syncPeers) {
// Not add new peers because IDs that are not on the server can't be synced, then sync will happen every startup.
for (var p in peers) {
Peer? r = recents.firstWhereOrNull((e) => e.id == p.id);
if (r != null) {
if (!peerSyncEqual(p, r)) {
await syncOnePeer(p, r);
}
await trySyncOnePeer(p, r);
}
}
// Pull cannot be used for sync to avoid cyclic sync.
if (uiUpdate && gFFI.abModel.currentName.value == profile.name) {
peers.refresh();
}
if (saveCache) {
gFFI.abModel._saveCache();
}
} catch (err) {
debugPrint('syncFromRecent err: ${err.toString()}');
}

View File

@ -352,13 +352,13 @@ class FfiModel with ChangeNotifier {
handleReloading(evt);
} else if (name == 'plugin_option') {
handleOption(evt);
} else if (name == "sync_peer_password_to_ab") {
} else if (name == "sync_peer_hash_password_to_personal_ab") {
if (desktopType == DesktopType.main) {
final id = evt['id'];
final password = evt['password'];
if (id != null && password != null) {
final hash = evt['hash'];
if (id != null && hash != null) {
gFFI.abModel
.changePersonalHashPassword(id.toString(), password.toString());
.changePersonalHashPassword(id.toString(), hash.toString());
}
}
} else if (name == "cm_file_transfer_log") {

View File

@ -61,7 +61,7 @@ class Peer {
};
}
Map<String, dynamic> toPersonalAbUploadJson(bool includingHash) {
Map<String, dynamic> toCustomJson({required bool includingHash}) {
var res = <String, dynamic>{
"id": id,
"username": username,
@ -76,32 +76,6 @@ class Peer {
return res;
}
Map<String, dynamic> toSharedAbUploadJson(bool includingPassword) {
var res = <String, dynamic>{
"id": id,
"username": username,
"hostname": hostname,
"platform": platform,
"alias": alias,
"tags": tags,
};
if (includingPassword) {
res['password'] = password;
}
return res;
}
Map<String, dynamic> toSharedAbCacheJson() {
return <String, dynamic>{
"id": id,
"username": username,
"hostname": hostname,
"platform": platform,
"alias": alias,
"tags": tags,
};
}
Map<String, dynamic> toGroupCacheJson() {
return <String, dynamic>{
"id": id,

View File

@ -1129,6 +1129,53 @@ impl VideoHandler {
}
}
// The source of sent password
#[derive(Debug, Clone, PartialEq, Eq)]
enum PasswordSource {
PersonalAb(Vec<u8>),
SharedAb(String),
Undefined,
}
impl Default for PasswordSource {
fn default() -> Self {
PasswordSource::Undefined
}
}
impl PasswordSource {
// Whether the password is personal ab password
pub fn is_personal_ab(&self, password: &[u8]) -> bool {
if password.is_empty() {
return false;
}
match self {
PasswordSource::PersonalAb(p) => p == password,
_ => false,
}
}
// Whether the password is shared ab password
pub fn is_shared_ab(&self, password: &[u8], hash: &Hash) -> bool {
if password.is_empty() {
return false;
}
match self {
PasswordSource::SharedAb(p) => Self::equal(p, password, hash),
_ => false,
}
}
// Whether the password equals to the connected password
fn equal(password: &str, connected_password: &[u8], hash: &Hash) -> bool {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&hash.salt);
let res = hasher.finalize();
connected_password[..] == res[..]
}
}
/// Login config handler for [`Client`].
#[derive(Default)]
pub struct LoginConfigHandler {
@ -1155,7 +1202,8 @@ pub struct LoginConfigHandler {
pub mark_unsupported: Vec<CodecFormat>,
pub selected_windows_session_id: Option<u32>,
pub peer_info: Option<PeerInfo>,
shared_password: Option<String>, // used to distinguish whether it is connected with a shared password
password_source: PasswordSource, // where the sent password comes from
shared_password: Option<String>, // Store the shared password
}
impl Deref for LoginConfigHandler {
@ -1829,20 +1877,25 @@ impl LoginConfigHandler {
platform: pi.platform.clone(),
};
let mut config = self.load_config();
let connected_with_shared_password = self.is_connected_with_shared_password();
let old_config_password = config.password.clone();
config.info = serde;
let password = self.password.clone();
let password0 = config.password.clone();
let remember = self.remember;
let hash = self.hash.clone();
if remember {
if !password.is_empty() && password != password0 {
config.password = password;
// remember is true: use PeerConfig password or ui login
// not sync shared password to recent
if !password.is_empty()
&& password != password0
&& !self.password_source.is_shared_ab(&password, &hash)
{
config.password = password.clone();
log::debug!("remember password of {}", self.id);
}
} else {
if self.save_ab_password_to_recent {
config.password = password;
if self.password_source.is_personal_ab(&password) {
// sync personal ab password to recent automatically
config.password = password.clone();
log::debug!("save ab password of {} to recent", self.id);
} else if !password0.is_empty() {
config.password = Default::default();
@ -1863,13 +1916,16 @@ impl LoginConfigHandler {
}
#[cfg(feature = "flutter")]
{
if !connected_with_shared_password && remember && !config.password.is_empty() {
// sync ab password with PeerConfig password
let password = base64::encode(config.password.clone(), base64::Variant::Original);
// sync connected password to personal ab automatically if it is not shared password
if !config.password.is_empty()
&& !self.password_source.is_shared_ab(&password, &hash)
&& !self.password_source.is_personal_ab(&password)
{
let hash = base64::encode(config.password.clone(), base64::Variant::Original);
let evt: HashMap<&str, String> = HashMap::from([
("name", "sync_peer_password_to_ab".to_string()),
("name", "sync_peer_hash_password_to_personal_ab".to_string()),
("id", self.id.clone()),
("password", password),
("hash", hash),
]);
let evt = serde_json::ser::to_string(&evt).unwrap_or("".to_owned());
crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, evt);
@ -1893,27 +1949,12 @@ impl LoginConfigHandler {
config.keyboard_mode = KeyboardMode::Legacy.to_string();
}
}
// keep hash password unchanged if connected with shared password
if connected_with_shared_password {
config.password = old_config_password;
}
// no matter if change, for update file time
self.save_config(config);
self.supported_encoding = pi.encoding.clone().unwrap_or_default();
log::info!("peer info supported_encoding:{:?}", self.supported_encoding);
}
fn is_connected_with_shared_password(&self) -> bool {
if let Some(shared_password) = self.shared_password.as_ref() {
let mut hasher = Sha256::new();
hasher.update(shared_password);
hasher.update(&self.hash.salt);
let res = hasher.finalize();
return self.password.clone()[..] == res[..];
}
false
}
pub fn get_remote_dir(&self) -> String {
serde_json::from_str::<HashMap<String, String>>(&self.get_option("remote_dir"))
.unwrap_or_default()
@ -2565,7 +2606,6 @@ pub fn handle_login_error(
err: &str,
interface: &impl Interface,
) -> bool {
lc.write().unwrap().save_ab_password_to_recent = false;
if err == LOGIN_MSG_PASSWORD_EMPTY {
lc.write().unwrap().password = Default::default();
interface.msgbox("input-password", "Password Required", "", "");
@ -2617,14 +2657,20 @@ pub async fn handle_hash(
peer: &mut Stream,
) {
lc.write().unwrap().hash = hash.clone();
// Take care of password application order
// switch_uuid
let uuid = lc.write().unwrap().switch_uuid.take();
if let Some(uuid) = uuid {
if let Ok(uuid) = uuid::Uuid::from_str(&uuid) {
send_switch_login_request(lc.clone(), peer, uuid).await;
lc.write().unwrap().password_source = Default::default();
return;
}
}
// last password
let mut password = lc.read().unwrap().password.clone();
// preset password
if password.is_empty() {
if !password_preset.is_empty() {
let mut hasher = Sha256::new();
@ -2632,32 +2678,33 @@ pub async fn handle_hash(
hasher.update(&hash.salt);
let res = hasher.finalize();
password = res[..].into();
lc.write().unwrap().password_source = Default::default();
}
}
// shared password
// Currently it's used only when click shared ab peer card
let shared_password = lc.write().unwrap().shared_password.take();
if let Some(shared_password) = shared_password {
if !shared_password.is_empty() {
let mut hasher = Sha256::new();
hasher.update(shared_password.clone());
hasher.update(&hash.salt);
let res = hasher.finalize();
password = res[..].into();
lc.write().unwrap().password_source = PasswordSource::SharedAb(shared_password);
}
}
// peer config password
if password.is_empty() {
password = lc.read().unwrap().config.password.clone();
}
if password.is_empty() {
let access_token = LocalConfig::get_option("access_token");
let ab = hbb_common::config::Ab::load();
if !access_token.is_empty() && access_token == ab.access_token {
let id = lc.read().unwrap().id.clone();
if let Some(ab) = ab.ab_entries.iter().find(|a| a.personal()) {
if let Some(p) = ab
.peers
.iter()
.find_map(|p| if p.id == id { Some(p) } else { None })
{
if let Ok(hash) = base64::decode(p.hash.clone(), base64::Variant::Original) {
if !hash.is_empty() {
password = hash;
lc.write().unwrap().save_ab_password_to_recent = true;
}
}
}
}
if !password.is_empty() {
lc.write().unwrap().password_source = Default::default();
}
}
// personal ab password
if password.is_empty() {
try_get_password_from_personal_ab(lc.clone(), &mut password);
}
lc.write().unwrap().password = password.clone();
let password = if password.is_empty() {
// login without password, the remote side can click accept
@ -2677,6 +2724,31 @@ pub async fn handle_hash(
lc.write().unwrap().hash = hash;
}
#[inline]
fn try_get_password_from_personal_ab(lc: Arc<RwLock<LoginConfigHandler>>, password: &mut Vec<u8>) {
let access_token = LocalConfig::get_option("access_token");
let ab = hbb_common::config::Ab::load();
if !access_token.is_empty() && access_token == ab.access_token {
let id = lc.read().unwrap().id.clone();
if let Some(ab) = ab.ab_entries.iter().find(|a| a.personal()) {
if let Some(p) = ab
.peers
.iter()
.find_map(|p| if p.id == id { Some(p) } else { None })
{
if let Ok(hash_password) = base64::decode(p.hash.clone(), base64::Variant::Original)
{
if !hash_password.is_empty() {
*password = hash_password.clone();
lc.write().unwrap().password_source =
PasswordSource::PersonalAb(hash_password);
}
}
}
}
}
}
/// Send login message to peer.
///
/// # Arguments
@ -2722,9 +2794,13 @@ pub async fn handle_login_from_ui(
let mut password2 = lc.read().unwrap().password.clone();
if password2.is_empty() {
password2 = lc.read().unwrap().config.password.clone();
if !password2.is_empty() {
lc.write().unwrap().password_source = Default::default();
}
}
password2
} else {
lc.write().unwrap().password_source = Default::default();
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&lc.read().unwrap().hash.salt);

View File

@ -1050,8 +1050,17 @@ pub fn session_add(
LocalConfig::set_remote_id(&id);
let mut preset_password = password.clone();
let shared_password = if is_shared_password {
// To achieve a flexible password application order, we dont' treat shared password as a preset password.
preset_password = Default::default();
Some(password)
} else {
None
};
let session: Session<FlutterHandler> = Session {
password: password.clone(),
password: preset_password,
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
@ -1069,11 +1078,6 @@ pub fn session_add(
#[cfg(not(feature = "gpucodec"))]
let adapter_luid = None;
let shared_password = if is_shared_password {
Some(password)
} else {
None
};
session.lc.write().unwrap().initialize(
id.to_owned(),
conn_type,