worf-warden improve auto type and config
* auto type now supports inserting a totp key with $T * configuration allows setting a different type tool * improve documentation
This commit is contained in:
parent
697587ab04
commit
04d11c98d7
2 changed files with 123 additions and 45 deletions
|
@ -6,18 +6,34 @@ Simple password manager build upon these additional tools aside worf
|
||||||
* [pinentry](https://www.gnupg.org/related_software/pinentry/index.en.html) is required to show a dialog show password entry
|
* [pinentry](https://www.gnupg.org/related_software/pinentry/index.en.html) is required to show a dialog show password entry
|
||||||
* As worf warden
|
* As worf warden
|
||||||
* [ydotool](https://github.com/ReimuNotMoe/ydotool)
|
* [ydotool](https://github.com/ReimuNotMoe/ydotool)
|
||||||
|
* ydotool is just the defaults, other tools can be configured via `--typing-cmd` or using this key in the config file.
|
||||||
|
|
||||||
The idea it taken from https://github.com/mattydebie/bitwarden-rofi/blob/master/bwmenu
|
The idea it taken from https://github.com/mattydebie/bitwarden-rofi/blob/master/bwmenu
|
||||||
|
|
||||||
## Custom auto typing
|
## Custom auto typing
|
||||||
* Auto typing supports custom keys. Just pass user name with `$U` and pw with `$P`
|
|
||||||
* I.e. the default is `$U\t$P` which is user, tab, password.
|
|
||||||
* This is using ydotool to type, so see their documentation for key input details.
|
|
||||||
|
|
||||||
### Example
|
Custom key strokes are supported for auto typing.
|
||||||
`~/.config/worf/warden`
|
For example this can be used for some websites like PayPal,
|
||||||
|
where `<enter>` after the username must be typed instead of `<tab>`
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
* `$U` -- Username
|
||||||
|
* `$P` -- Password
|
||||||
|
* `$T` -- Two factor
|
||||||
|
* `$S` -- Sleep in milliseconds
|
||||||
|
* `_` -- All underscores are removed and used to make the string more readable
|
||||||
|
|
||||||
|
The default is `$U\t$P` which is user, tab, password.
|
||||||
|
As the string is passed to the typing tool, see their documentation for special chars.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The location of the configuration file follows the same rules as worf itself.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
typing_cmd = "ydotool"
|
||||||
|
typing_cmd_args = ["type"]
|
||||||
|
|
||||||
[custom_auto_types]
|
[custom_auto_types]
|
||||||
# This will use User, enter, password for the demo entry.
|
# This will use User, enter, password for the demo entry.
|
||||||
# You can use the id or the label as key, where id has higher precedence.
|
# You can use the id or the label as key, where id has higher precedence.
|
||||||
|
|
|
@ -114,49 +114,92 @@ fn groups() -> String {
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_type(text: &str) {
|
fn keyboard_type(text: &str, cfg: &WardenConfig) {
|
||||||
Command::new("ydotool")
|
let mut cmd = Command::new(cfg.typing_cmd());
|
||||||
.arg("type")
|
for arg in cfg.typing_cmd_args() {
|
||||||
.arg(text)
|
cmd.arg(arg);
|
||||||
.output()
|
}
|
||||||
.expect("Failed to execute ydotool");
|
cmd.arg(text);
|
||||||
|
|
||||||
|
cmd.output()
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to execute {}", cfg.typing_cmd()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cmd(cmd: &str) -> (&str, Option<u64>, Option<&str>) {
|
fn keyboard_return(config: &WardenConfig) {
|
||||||
if let Some(pos) = cmd.find("$S") {
|
keyboard_type("\n", config);
|
||||||
let left = &cmd[..pos];
|
}
|
||||||
let rest = &cmd[pos + 2..]; // Skip "$S"
|
|
||||||
|
|
||||||
// Extract digits after "$S"
|
fn keyboard_auto_type(cmd: &str, id: &str, config: &WardenConfig) -> Result<(), String> {
|
||||||
let num_part: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
|
let mut input = cmd.replace('_', "");
|
||||||
|
|
||||||
if let Ok(number) = num_part.parse::<u64>() {
|
//.replace("$U", &user).replace("$P", &pw);
|
||||||
let right = &rest[num_part.len()..];
|
|
||||||
return (left, Some(number), Some(right));
|
// if input.contains("$T") {
|
||||||
|
// input = input.replace("$T", &rbw_get_totp(id, false)?);
|
||||||
|
// }
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
// Remove up to and including the first '$'
|
||||||
|
|
||||||
|
if let Some(pos) = input.find('$') {
|
||||||
|
// Extract substring before '$'
|
||||||
|
let extracted = input[..pos].to_string();
|
||||||
|
|
||||||
|
// Remove extracted part + '$' from input
|
||||||
|
input.drain(..=pos);
|
||||||
|
|
||||||
|
if !extracted.is_empty() {
|
||||||
|
keyboard_type(extracted.as_str(), config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(cmd, None, None)
|
// Match the next character
|
||||||
}
|
match input.chars().next() {
|
||||||
|
Some('S') => {
|
||||||
|
// Remove the 'S' command character
|
||||||
|
input.remove(0);
|
||||||
|
|
||||||
fn keyboard_return() {
|
// Collect digits following 'S'
|
||||||
keyboard_type("\n");
|
let digits: String = input.chars().take_while(|c| c.is_ascii_digit()).collect();
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboard_auto_type(cmd: &str, id: &str) -> Result<(), String> {
|
// Remove the digits from input
|
||||||
let user = rbw_get_user(id, false)?;
|
let len = digits.len();
|
||||||
let pw = rbw_get_password(id, false)?;
|
input.drain(..len);
|
||||||
|
|
||||||
let ydo_string = cmd.replace('_', "").replace("$U", &user).replace("$P", &pw);
|
// Parse and sleep
|
||||||
|
if let Ok(ms) = digits.parse::<u64>() {
|
||||||
|
sleep(Duration::from_millis(ms));
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to parse digits: {digits}");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let (left, sleep_ms, right) = parse_cmd(&ydo_string);
|
Some('U') => {
|
||||||
keyboard_type(left);
|
let user = rbw_get_user(id, false)?;
|
||||||
if let Some(sleep_ms) = sleep_ms {
|
keyboard_type(&user, config);
|
||||||
sleep(Duration::from_millis(sleep_ms));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(right) = right {
|
Some('P') => {
|
||||||
keyboard_type(right);
|
let pw = rbw_get_password(id, false)?;
|
||||||
|
keyboard_type(&pw, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some('T') => {
|
||||||
|
let totp = rbw_get_totp(id, false)?;
|
||||||
|
keyboard_type(&totp, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(c) => {
|
||||||
|
log::error!("Unknown character found: {c}");
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
log::error!("No command found after '$'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.drain(..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -359,13 +402,13 @@ fn show(
|
||||||
.get(id)
|
.get(id)
|
||||||
.or(warden_config.custom_auto_types.get(&selection.menu.label))
|
.or(warden_config.custom_auto_types.get(&selection.menu.label))
|
||||||
.unwrap_or(&default);
|
.unwrap_or(&default);
|
||||||
keyboard_auto_type(typing, id)?;
|
keyboard_auto_type(typing, id, &warden_config)?;
|
||||||
} else if key == key_type_user() || key == key_type_user_and_enter() {
|
} else if key == key_type_user() || key == key_type_user_and_enter() {
|
||||||
keyboard_type(&rbw_get_user(id, false)?);
|
keyboard_type(&rbw_get_user(id, false)?, &warden_config);
|
||||||
} else if key == key_type_password() || key == key_type_password_and_enter() {
|
} else if key == key_type_password() || key == key_type_password_and_enter() {
|
||||||
keyboard_type(&rbw_get_password(id, false)?);
|
keyboard_type(&rbw_get_password(id, false)?, &warden_config);
|
||||||
} else if key == key_type_totp() || key == key_type_totp_and_enter() {
|
} else if key == key_type_totp() || key == key_type_totp_and_enter() {
|
||||||
keyboard_type(&rbw_get_totp(id, false)?);
|
keyboard_type(&rbw_get_totp(id, false)?, &warden_config);
|
||||||
} else if key == key_lock() {
|
} else if key == key_lock() {
|
||||||
rbw("lock", None)?;
|
rbw("lock", None)?;
|
||||||
} else if key == key_sync() {
|
} else if key == key_sync() {
|
||||||
|
@ -375,7 +418,7 @@ fn show(
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.modifiers.contains(&Modifier::Shift) {
|
if key.modifiers.contains(&Modifier::Shift) {
|
||||||
keyboard_return();
|
keyboard_return(&warden_config);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let pw = rbw_get_password(id, true)?;
|
let pw = rbw_get_password(id, true)?;
|
||||||
|
@ -394,9 +437,23 @@ fn show(
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||||
struct WardenConfig {
|
struct WardenConfig {
|
||||||
|
typing_cmd: Option<String>,
|
||||||
|
typing_cmd_args: Option<Vec<String>>,
|
||||||
custom_auto_types: HashMap<String, String>,
|
custom_auto_types: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WardenConfig {
|
||||||
|
fn typing_cmd(&self) -> String {
|
||||||
|
self.typing_cmd.clone().unwrap_or("ydotool".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typing_cmd_args(&self) -> Vec<String> {
|
||||||
|
self.typing_cmd_args
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(vec!["type".to_owned()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser, Clone)]
|
#[derive(Debug, Parser, Clone)]
|
||||||
struct WardenArgs {
|
struct WardenArgs {
|
||||||
/// Configuration file for worf warden
|
/// Configuration file for worf warden
|
||||||
|
@ -431,9 +488,14 @@ fn main() -> Result<(), String> {
|
||||||
std::process::exit(1)
|
std::process::exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// will exit if there is a daemon running already, so it's fine to call this everytime.
|
// ydotool is our special default value, give it some love and start the daemon
|
||||||
if let Err(e) = spawn_fork("ydotoold", None) {
|
// if other tools need this it must be run beforehand (or can be added here)
|
||||||
log::error!("Failed to start ydotool daemon: {e}");
|
// in case another tool is added it might make sense to make it configurable
|
||||||
|
if warden_config.typing_cmd() == "ydotool" {
|
||||||
|
// will exit if there is a daemon running already, so it's fine to call this everytime.
|
||||||
|
if let Err(e) = spawn_fork("ydotoold", None) {
|
||||||
|
log::error!("Failed to start ydotool daemon: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let worf_config = Arc::new(RwLock::new(cfg.worf.clone()));
|
let worf_config = Arc::new(RwLock::new(cfg.worf.clone()));
|
||||||
|
|
Loading…
Add table
Reference in a new issue