feat: improved password workflow

This commit is contained in:
Jonathan Capps 2025-04-16 20:38:15 +01:00 committed by Michael Murphy
parent 2397a003d7
commit a045947f12
2 changed files with 162 additions and 49 deletions

View file

@ -32,8 +32,9 @@ pub struct User {
id: u64,
profile_icon: Option<icon::Handle>,
full_name: String,
password: String,
username: String,
password: String,
password_confirm: String,
full_name_edit: bool,
password_edit: bool,
username_edit: bool,
@ -43,13 +44,13 @@ pub struct User {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EditorField {
FullName,
Password,
Username,
}
#[derive(Clone, Debug)]
pub enum Dialog {
AddNewUser(User),
UpdatePassword(User),
}
#[derive(Clone, Debug)]
@ -62,8 +63,11 @@ pub struct Page {
dialog: Option<Dialog>,
default_icon: icon::Handle,
password_label: String,
password_confirm_label: String,
username_label: String,
fullname_label: String,
password_hidden: bool,
password_confirm_hidden: bool,
}
impl Default for Page {
@ -77,8 +81,11 @@ impl Default for Page {
dialog: None,
default_icon: icon::from_path(PathBuf::from(DEFAULT_ICON_FILE)),
password_label: crate::fl!("password"),
password_confirm_label: crate::fl!("password-confirm"),
username_label: crate::fl!("username"),
fullname_label: crate::fl!("full-name"),
password_hidden: true,
password_confirm_hidden: true,
}
}
}
@ -99,6 +106,9 @@ pub enum Message {
SelectedUserDelete(u64),
SelectedUserSetAdmin(u64, bool),
ToggleEdit(usize, EditorField),
TogglePasswordVisibility,
TogglePasswordConfirmVisibility,
SaveNewPassword(User),
}
impl From<Message> for crate::app::Message {
@ -159,15 +169,35 @@ impl page::Page<crate::pages::Message> for Page {
);
let password_input = widget::container(
widget::text_input("", &user.password)
.password()
.label(&self.password_label)
.on_input(|value| {
Message::Dialog(Some(Dialog::AddNewUser(User {
password: value,
..user.clone()
})))
}),
widget::secure_input(
"",
&user.password,
Some(Message::TogglePasswordVisibility),
self.password_hidden,
)
.label(&self.password_label)
.on_input(|value| {
Message::Dialog(Some(Dialog::AddNewUser(User {
password: value,
..user.clone()
})))
}),
);
let password_confirm_input = widget::container(
widget::secure_input(
"",
&user.password_confirm,
Some(Message::TogglePasswordConfirmVisibility),
self.password_confirm_hidden,
)
.label(&self.password_confirm_label)
.on_input(|value| {
Message::Dialog(Some(Dialog::AddNewUser(User {
password_confirm: value,
..user.clone()
})))
}),
);
let admin_toggler = widget::toggler(user.is_admin).on_toggle(|value| {
@ -184,9 +214,16 @@ impl page::Page<crate::pages::Message> for Page {
let complete_maybe = if !username_valid && !user.username.is_empty() {
validation_msg = fl!("invalid-username");
None
} else if user.password != user.password_confirm
&& user.password != ""
&& user.password_confirm != ""
{
validation_msg = fl!("password-mismatch");
None
} else if user.full_name.is_empty()
|| user.username.is_empty()
|| user.password.is_empty()
|| user.password_confirm.is_empty()
{
None
} else {
@ -212,6 +249,7 @@ impl page::Page<crate::pages::Message> for Page {
.add(full_name_input)
.add(username_input)
.add(password_input)
.add(password_confirm_input)
.add(
row::with_capacity(3)
.push(
@ -232,6 +270,73 @@ impl page::Page<crate::pages::Message> for Page {
.tertiary_action(widget::text::body(validation_msg))
.apply(Element::from)
}
Dialog::UpdatePassword(user) => {
let password_input = widget::container(
widget::secure_input(
"",
&user.password,
Some(Message::TogglePasswordVisibility),
self.password_hidden,
)
.label(&self.password_label)
.on_input(|value| {
Message::Dialog(Some(Dialog::UpdatePassword(User {
password: value,
..user.clone()
})))
}),
);
let password_confirm_input = widget::container(
widget::secure_input(
"",
&user.password_confirm,
Some(Message::TogglePasswordConfirmVisibility),
self.password_confirm_hidden,
)
.label(&self.password_confirm_label)
.on_input(|value| {
Message::Dialog(Some(Dialog::UpdatePassword(User {
password_confirm: value,
..user.clone()
})))
}),
);
// validation
let mut validation_msg = String::new();
let complete_maybe = if user.password != user.password_confirm
&& user.password != ""
&& user.password_confirm != ""
{
validation_msg = fl!("password-mismatch");
None
} else if user.password.is_empty() || user.password_confirm.is_empty() {
None
} else {
Some(Message::SaveNewPassword(user.clone()))
};
let save_button = widget::button::suggested(fl!("save"))
.on_press_maybe(complete_maybe)
.apply(Element::from);
let cancel_button =
widget::button::standard(fl!("cancel")).on_press(Message::Dialog(None));
widget::dialog()
.title(fl!("change-password"))
.control(
widget::ListColumn::default()
.add(password_input)
.add(password_confirm_input),
)
.primary_action(save_button)
.secondary_action(cancel_button)
.tertiary_action(widget::text::body(validation_msg))
.apply(Element::from)
}
};
dialog_element.map(crate::pages::Message::User).into()
@ -293,6 +398,7 @@ impl Page {
username: String::from(user.username),
full_name: String::from(user.full_name),
password: String::new(),
password_confirm: String::new(),
full_name_edit: false,
password_edit: false,
username_edit: false,
@ -379,7 +485,6 @@ impl Page {
if let Some(user) = self.users.get_mut(id) {
match field {
EditorField::FullName => user.full_name = value,
EditorField::Password => user.password = value,
EditorField::Username => user.username = value,
}
}
@ -389,12 +494,18 @@ impl Page {
if let Some(user) = self.users.get_mut(id) {
match field {
EditorField::FullName => user.full_name_edit = !user.full_name_edit,
EditorField::Password => user.password_edit = !user.password_edit,
EditorField::Username => user.username_edit = !user.username_edit,
}
}
}
Message::TogglePasswordVisibility => {
self.password_hidden = !self.password_hidden;
}
Message::TogglePasswordConfirmVisibility => {
self.password_confirm_hidden = !self.password_confirm_hidden;
}
Message::ApplyEdit(id, field) => {
if let Some(user) = self.users.get_mut(id) {
let uid = user.id;
@ -420,34 +531,6 @@ impl Page {
.discard();
}
EditorField::Password => {
let password = std::mem::take(&mut user.password);
return cosmic::Task::future(async move {
let Ok(conn) = zbus::Connection::system().await else {
return;
};
let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await
else {
return;
};
match request_permission_on_denial(&conn, || {
user.set_password(&password, "")
})
.await
{
Err(why) => {
tracing::error!(?why, "failed to set password");
}
Ok(_) => (),
}
})
.discard();
}
EditorField::Username => {
let username = user.username.clone();
return cosmic::Task::future(async move {
@ -471,6 +554,34 @@ impl Page {
}
}
Message::SaveNewPassword(user) => {
self.dialog = None;
let uid = user.id;
let password = user.password;
return cosmic::Task::future(async move {
let Ok(conn) = zbus::Connection::system().await else {
return;
};
let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await else {
return;
};
match request_permission_on_denial(&conn, || user.set_password(&password, ""))
.await
{
Err(why) => {
tracing::error!(?why, "failed to set password");
}
Ok(_) => (),
}
})
.discard();
}
Message::LoadPage(uid, users) => {
self.current_user_id = uid;
self.users = users;
@ -514,6 +625,8 @@ impl Page {
}
Message::Dialog(dialog) => {
self.password_hidden = true;
self.password_confirm_hidden = true;
self.dialog = dialog;
}
@ -617,13 +730,9 @@ fn user_list() -> Section<crate::pages::Message> {
.on_input(move |name| Message::Edit(idx, EditorField::Username, name))
.on_submit(move |_| Message::ApplyEdit(idx, EditorField::Username));
let password =
widget::editable_input("", &user.password, user.password_edit, move |_| {
Message::ToggleEdit(idx, EditorField::Password)
})
.on_input(move |pass| Message::Edit(idx, EditorField::Password, pass))
.on_submit(move |_| Message::ApplyEdit(idx, EditorField::Password))
.password();
let password = widget::button::standard(fl!("change-password"))
.on_press(Message::Dialog(Some(Dialog::UpdatePassword(user.clone()))))
.apply(Element::from);
let fullname = widget::editable_input(
"",

View file

@ -30,6 +30,7 @@ network-and-wireless = Network & Wireless
no-networks = No networks have been found.
no-vpn = No VPN connections available.
password = Password
password-confirm = Confirm Password
remove = Remove
settings = Settings
username = Username
@ -888,6 +889,9 @@ administrator = Administrator
.desc = Administrators can change settings for all users, add and remove other users.
add-user = Add user
change-password = Change password
remove-user = Remove user
full-name = Full name
invalid-username = Invalid username
invalid-username = Invalid username.
password-mismatch = Password and confirmation must match.
save = Save