feat: improved password workflow
This commit is contained in:
parent
2397a003d7
commit
a045947f12
2 changed files with 162 additions and 49 deletions
|
|
@ -32,8 +32,9 @@ pub struct User {
|
||||||
id: u64,
|
id: u64,
|
||||||
profile_icon: Option<icon::Handle>,
|
profile_icon: Option<icon::Handle>,
|
||||||
full_name: String,
|
full_name: String,
|
||||||
password: String,
|
|
||||||
username: String,
|
username: String,
|
||||||
|
password: String,
|
||||||
|
password_confirm: String,
|
||||||
full_name_edit: bool,
|
full_name_edit: bool,
|
||||||
password_edit: bool,
|
password_edit: bool,
|
||||||
username_edit: bool,
|
username_edit: bool,
|
||||||
|
|
@ -43,13 +44,13 @@ pub struct User {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum EditorField {
|
pub enum EditorField {
|
||||||
FullName,
|
FullName,
|
||||||
Password,
|
|
||||||
Username,
|
Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Dialog {
|
pub enum Dialog {
|
||||||
AddNewUser(User),
|
AddNewUser(User),
|
||||||
|
UpdatePassword(User),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -62,8 +63,11 @@ pub struct Page {
|
||||||
dialog: Option<Dialog>,
|
dialog: Option<Dialog>,
|
||||||
default_icon: icon::Handle,
|
default_icon: icon::Handle,
|
||||||
password_label: String,
|
password_label: String,
|
||||||
|
password_confirm_label: String,
|
||||||
username_label: String,
|
username_label: String,
|
||||||
fullname_label: String,
|
fullname_label: String,
|
||||||
|
password_hidden: bool,
|
||||||
|
password_confirm_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Page {
|
impl Default for Page {
|
||||||
|
|
@ -77,8 +81,11 @@ impl Default for Page {
|
||||||
dialog: None,
|
dialog: None,
|
||||||
default_icon: icon::from_path(PathBuf::from(DEFAULT_ICON_FILE)),
|
default_icon: icon::from_path(PathBuf::from(DEFAULT_ICON_FILE)),
|
||||||
password_label: crate::fl!("password"),
|
password_label: crate::fl!("password"),
|
||||||
|
password_confirm_label: crate::fl!("password-confirm"),
|
||||||
username_label: crate::fl!("username"),
|
username_label: crate::fl!("username"),
|
||||||
fullname_label: crate::fl!("full-name"),
|
fullname_label: crate::fl!("full-name"),
|
||||||
|
password_hidden: true,
|
||||||
|
password_confirm_hidden: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +106,9 @@ pub enum Message {
|
||||||
SelectedUserDelete(u64),
|
SelectedUserDelete(u64),
|
||||||
SelectedUserSetAdmin(u64, bool),
|
SelectedUserSetAdmin(u64, bool),
|
||||||
ToggleEdit(usize, EditorField),
|
ToggleEdit(usize, EditorField),
|
||||||
|
TogglePasswordVisibility,
|
||||||
|
TogglePasswordConfirmVisibility,
|
||||||
|
SaveNewPassword(User),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Message> for crate::app::Message {
|
impl From<Message> for crate::app::Message {
|
||||||
|
|
@ -159,15 +169,35 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
);
|
);
|
||||||
|
|
||||||
let password_input = widget::container(
|
let password_input = widget::container(
|
||||||
widget::text_input("", &user.password)
|
widget::secure_input(
|
||||||
.password()
|
"",
|
||||||
.label(&self.password_label)
|
&user.password,
|
||||||
.on_input(|value| {
|
Some(Message::TogglePasswordVisibility),
|
||||||
Message::Dialog(Some(Dialog::AddNewUser(User {
|
self.password_hidden,
|
||||||
password: value,
|
)
|
||||||
..user.clone()
|
.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| {
|
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() {
|
let complete_maybe = if !username_valid && !user.username.is_empty() {
|
||||||
validation_msg = fl!("invalid-username");
|
validation_msg = fl!("invalid-username");
|
||||||
None
|
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()
|
} else if user.full_name.is_empty()
|
||||||
|| user.username.is_empty()
|
|| user.username.is_empty()
|
||||||
|| user.password.is_empty()
|
|| user.password.is_empty()
|
||||||
|
|| user.password_confirm.is_empty()
|
||||||
{
|
{
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -212,6 +249,7 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
.add(full_name_input)
|
.add(full_name_input)
|
||||||
.add(username_input)
|
.add(username_input)
|
||||||
.add(password_input)
|
.add(password_input)
|
||||||
|
.add(password_confirm_input)
|
||||||
.add(
|
.add(
|
||||||
row::with_capacity(3)
|
row::with_capacity(3)
|
||||||
.push(
|
.push(
|
||||||
|
|
@ -232,6 +270,73 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
.tertiary_action(widget::text::body(validation_msg))
|
.tertiary_action(widget::text::body(validation_msg))
|
||||||
.apply(Element::from)
|
.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()
|
dialog_element.map(crate::pages::Message::User).into()
|
||||||
|
|
@ -293,6 +398,7 @@ impl Page {
|
||||||
username: String::from(user.username),
|
username: String::from(user.username),
|
||||||
full_name: String::from(user.full_name),
|
full_name: String::from(user.full_name),
|
||||||
password: String::new(),
|
password: String::new(),
|
||||||
|
password_confirm: String::new(),
|
||||||
full_name_edit: false,
|
full_name_edit: false,
|
||||||
password_edit: false,
|
password_edit: false,
|
||||||
username_edit: false,
|
username_edit: false,
|
||||||
|
|
@ -379,7 +485,6 @@ impl Page {
|
||||||
if let Some(user) = self.users.get_mut(id) {
|
if let Some(user) = self.users.get_mut(id) {
|
||||||
match field {
|
match field {
|
||||||
EditorField::FullName => user.full_name = value,
|
EditorField::FullName => user.full_name = value,
|
||||||
EditorField::Password => user.password = value,
|
|
||||||
EditorField::Username => user.username = value,
|
EditorField::Username => user.username = value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -389,12 +494,18 @@ impl Page {
|
||||||
if let Some(user) = self.users.get_mut(id) {
|
if let Some(user) = self.users.get_mut(id) {
|
||||||
match field {
|
match field {
|
||||||
EditorField::FullName => user.full_name_edit = !user.full_name_edit,
|
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,
|
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) => {
|
Message::ApplyEdit(id, field) => {
|
||||||
if let Some(user) = self.users.get_mut(id) {
|
if let Some(user) = self.users.get_mut(id) {
|
||||||
let uid = user.id;
|
let uid = user.id;
|
||||||
|
|
@ -420,34 +531,6 @@ impl Page {
|
||||||
.discard();
|
.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 => {
|
EditorField::Username => {
|
||||||
let username = user.username.clone();
|
let username = user.username.clone();
|
||||||
return cosmic::Task::future(async move {
|
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) => {
|
Message::LoadPage(uid, users) => {
|
||||||
self.current_user_id = uid;
|
self.current_user_id = uid;
|
||||||
self.users = users;
|
self.users = users;
|
||||||
|
|
@ -514,6 +625,8 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Dialog(dialog) => {
|
Message::Dialog(dialog) => {
|
||||||
|
self.password_hidden = true;
|
||||||
|
self.password_confirm_hidden = true;
|
||||||
self.dialog = dialog;
|
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_input(move |name| Message::Edit(idx, EditorField::Username, name))
|
||||||
.on_submit(move |_| Message::ApplyEdit(idx, EditorField::Username));
|
.on_submit(move |_| Message::ApplyEdit(idx, EditorField::Username));
|
||||||
|
|
||||||
let password =
|
let password = widget::button::standard(fl!("change-password"))
|
||||||
widget::editable_input("", &user.password, user.password_edit, move |_| {
|
.on_press(Message::Dialog(Some(Dialog::UpdatePassword(user.clone()))))
|
||||||
Message::ToggleEdit(idx, EditorField::Password)
|
.apply(Element::from);
|
||||||
})
|
|
||||||
.on_input(move |pass| Message::Edit(idx, EditorField::Password, pass))
|
|
||||||
.on_submit(move |_| Message::ApplyEdit(idx, EditorField::Password))
|
|
||||||
.password();
|
|
||||||
|
|
||||||
let fullname = widget::editable_input(
|
let fullname = widget::editable_input(
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ network-and-wireless = Network & Wireless
|
||||||
no-networks = No networks have been found.
|
no-networks = No networks have been found.
|
||||||
no-vpn = No VPN connections available.
|
no-vpn = No VPN connections available.
|
||||||
password = Password
|
password = Password
|
||||||
|
password-confirm = Confirm Password
|
||||||
remove = Remove
|
remove = Remove
|
||||||
settings = Settings
|
settings = Settings
|
||||||
username = Username
|
username = Username
|
||||||
|
|
@ -888,6 +889,9 @@ administrator = Administrator
|
||||||
.desc = Administrators can change settings for all users, add and remove other users.
|
.desc = Administrators can change settings for all users, add and remove other users.
|
||||||
|
|
||||||
add-user = Add user
|
add-user = Add user
|
||||||
|
change-password = Change password
|
||||||
remove-user = Remove user
|
remove-user = Remove user
|
||||||
full-name = Full name
|
full-name = Full name
|
||||||
invalid-username = Invalid username
|
invalid-username = Invalid username.
|
||||||
|
password-mismatch = Password and confirmation must match.
|
||||||
|
save = Save
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue