feat: support keyfiles
I don't personally use them, so this is untested, but I imagine it works. in addition, I removed an unecessary bool "db_loaded", changed the style of the picker buttons based on whether the files exist, and improved ordering of the State struct
This commit is contained in:
parent
f7340ffcb9
commit
6e7d50a79d
1 changed files with 76 additions and 11 deletions
|
|
@ -17,6 +17,10 @@ enum Message {
|
||||||
PickDb,
|
PickDb,
|
||||||
PickedDb(Option<FileHandle>),
|
PickedDb(Option<FileHandle>),
|
||||||
|
|
||||||
|
ChangeKeyfilePath(String),
|
||||||
|
PickKeyfile,
|
||||||
|
PickedKeyfile(Option<FileHandle>),
|
||||||
|
|
||||||
ChangePassword(String),
|
ChangePassword(String),
|
||||||
ToggleSecurity,
|
ToggleSecurity,
|
||||||
OpenDb,
|
OpenDb,
|
||||||
|
|
@ -48,18 +52,19 @@ struct State {
|
||||||
db_path: String,
|
db_path: String,
|
||||||
db_path_exists: bool,
|
db_path_exists: bool,
|
||||||
|
|
||||||
|
keyfile_path: String,
|
||||||
|
keyfile_path_exists: bool,
|
||||||
|
|
||||||
|
is_secure: bool,
|
||||||
|
password: String,
|
||||||
|
|
||||||
db: Option<keepass::Database>,
|
db: Option<keepass::Database>,
|
||||||
|
|
||||||
db_loaded: bool,
|
|
||||||
db_view: Vec<keepass::db::Entry>,
|
db_view: Vec<keepass::db::Entry>,
|
||||||
selected_entry: usize,
|
selected_entry: usize,
|
||||||
selected_field: db::BasicField,
|
selected_field: db::BasicField,
|
||||||
query: String,
|
query: String,
|
||||||
|
|
||||||
password: String,
|
|
||||||
|
|
||||||
is_secure: bool,
|
|
||||||
|
|
||||||
status: Cow<'static, str>,
|
status: Cow<'static, str>,
|
||||||
error: Cow<'static, str>,
|
error: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +108,22 @@ impl State {
|
||||||
}
|
}
|
||||||
Message::PickedDb(None) => eprintln!("no path received from picker"),
|
Message::PickedDb(None) => eprintln!("no path received from picker"),
|
||||||
|
|
||||||
|
Message::ChangeKeyfilePath(keyfile_path) => {
|
||||||
|
self.keyfile_path_exists = fs::exists(&keyfile_path)
|
||||||
|
.inspect_err(|e| eprintln!("failed to check '{keyfile_path}': {e}"))
|
||||||
|
.is_ok_and(|exists| exists);
|
||||||
|
self.keyfile_path = keyfile_path
|
||||||
|
}
|
||||||
|
Message::PickKeyfile => {
|
||||||
|
return Task::perform(AsyncFileDialog::new().pick_file(), Message::PickedKeyfile);
|
||||||
|
}
|
||||||
|
Message::PickedKeyfile(Some(file)) => {
|
||||||
|
return Task::done(Message::ChangeKeyfilePath(
|
||||||
|
file.path().to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Message::PickedKeyfile(None) => eprintln!("no path received from picker"),
|
||||||
|
|
||||||
Message::ChangePassword(password) => self.password = password,
|
Message::ChangePassword(password) => self.password = password,
|
||||||
Message::ToggleSecurity => self.is_secure = !self.is_secure,
|
Message::ToggleSecurity => self.is_secure = !self.is_secure,
|
||||||
Message::OpenDb => return self.open_db(),
|
Message::OpenDb => return self.open_db(),
|
||||||
|
|
@ -113,7 +134,6 @@ impl State {
|
||||||
self.error = "".into();
|
self.error = "".into();
|
||||||
self.db = Some(db);
|
self.db = Some(db);
|
||||||
self.populate_view();
|
self.populate_view();
|
||||||
self.db_loaded = true;
|
|
||||||
}
|
}
|
||||||
Err(e) => self.error = e.into(),
|
Err(e) => self.error = e.into(),
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +192,7 @@ impl State {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
fn view(&self) -> Element<'_> {
|
fn view(&self) -> Element<'_> {
|
||||||
let main = if self.db_loaded {
|
let main = if self.db.is_some() {
|
||||||
widget::column([
|
widget::column([
|
||||||
widget::text_input("search query", &self.query)
|
widget::text_input("search query", &self.query)
|
||||||
.on_input(Message::ChangeQuery)
|
.on_input(Message::ChangeQuery)
|
||||||
|
|
@ -263,7 +283,30 @@ impl State {
|
||||||
widget::text_input("database path", &self.db_path)
|
widget::text_input("database path", &self.db_path)
|
||||||
.on_input(Message::ChangeDbPath)
|
.on_input(Message::ChangeDbPath)
|
||||||
.into(),
|
.into(),
|
||||||
widget::button("pick file").on_press(Message::PickDb).into(),
|
widget::button("pick db")
|
||||||
|
.style(if self.db_path_exists {
|
||||||
|
widget::button::primary
|
||||||
|
} else {
|
||||||
|
widget::button::danger
|
||||||
|
})
|
||||||
|
.on_press(Message::PickDb)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
widget::row([
|
||||||
|
widget::text_input("keyfile path", &self.keyfile_path)
|
||||||
|
.on_input(Message::ChangeKeyfilePath)
|
||||||
|
.into(),
|
||||||
|
widget::button("pick keyfile")
|
||||||
|
.style(
|
||||||
|
if self.keyfile_path.is_empty() || self.keyfile_path_exists {
|
||||||
|
widget::button::primary
|
||||||
|
} else {
|
||||||
|
widget::button::danger
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.on_press(Message::PickKeyfile)
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.into(),
|
.into(),
|
||||||
widget::row([
|
widget::row([
|
||||||
|
|
@ -279,7 +322,9 @@ impl State {
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.into(),
|
.into(),
|
||||||
widget::container(widget::button("open database").on_press_maybe(
|
widget::container(widget::button("open database").on_press_maybe(
|
||||||
if self.db_path_exists {
|
if self.db_path_exists
|
||||||
|
&& (self.keyfile_path.is_empty() || self.keyfile_path_exists)
|
||||||
|
{
|
||||||
Some(Message::OpenDb)
|
Some(Message::OpenDb)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -376,12 +421,32 @@ impl State {
|
||||||
|
|
||||||
self.status = "loading...".into();
|
self.status = "loading...".into();
|
||||||
|
|
||||||
let (db_path, password) = (self.db_path.clone(), self.password.clone());
|
let (db_path, keyfile_path, password) = (
|
||||||
|
self.db_path.clone(),
|
||||||
|
self.keyfile_path.clone(),
|
||||||
|
self.password.clone(),
|
||||||
|
);
|
||||||
Task::perform(
|
Task::perform(
|
||||||
async move {
|
async move {
|
||||||
|
let key = {
|
||||||
|
let mut key = keepass::DatabaseKey::new();
|
||||||
|
if !password.is_empty() {
|
||||||
|
key = key.with_password(&password);
|
||||||
|
}
|
||||||
|
if !keyfile_path.is_empty() {
|
||||||
|
key = key
|
||||||
|
.with_keyfile(
|
||||||
|
&mut fs::File::open(&keyfile_path).map_err(|e| e.to_string())?,
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
key
|
||||||
|
};
|
||||||
|
|
||||||
let db = keepass::Database::open(
|
let db = keepass::Database::open(
|
||||||
&mut fs::File::open(&db_path).map_err(|e| e.to_string())?,
|
&mut fs::File::open(&db_path).map_err(|e| e.to_string())?,
|
||||||
keepass::DatabaseKey::new().with_password(&password),
|
key,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue