feat(notsecrets): add age key retrieval with bw/file/prompt sources
This commit is contained in:
57
crates/notsecrets/src/sources/bitwarden.rs
Normal file
57
crates/notsecrets/src/sources/bitwarden.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use anyhow::{bail, Result};
|
||||
use std::process::Command;
|
||||
use which::which;
|
||||
use crate::AgeKeySource;
|
||||
|
||||
pub struct BitwardenSource {
|
||||
pub item_name: String,
|
||||
}
|
||||
|
||||
impl BitwardenSource {
|
||||
pub fn new(item_name: impl Into<String>) -> Self {
|
||||
Self { item_name: item_name.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl AgeKeySource for BitwardenSource {
|
||||
fn name(&self) -> &str { "bitwarden" }
|
||||
|
||||
fn retrieve(&self) -> Result<String> {
|
||||
if which("bw").is_err() {
|
||||
bail!("bw CLI not found in PATH");
|
||||
}
|
||||
|
||||
let session = std::env::var("BW_SESSION").unwrap_or_default();
|
||||
let session = if session.is_empty() {
|
||||
let password = rpassword::prompt_password("Bitwarden master password: ")
|
||||
.map_err(|e| anyhow::anyhow!("could not read password: {e}"))?;
|
||||
let output = Command::new("bw")
|
||||
.args(["unlock", "--raw", &password])
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
bail!("bw unlock failed: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
String::from_utf8(output.stdout)?.trim().to_string()
|
||||
} else {
|
||||
session
|
||||
};
|
||||
|
||||
let output = Command::new("bw")
|
||||
.args(["get", "notes", &self.item_name, "--session", &session])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"bw get notes '{}' failed: {}",
|
||||
self.item_name,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let key = String::from_utf8(output.stdout)?.trim().to_string();
|
||||
if key.is_empty() {
|
||||
bail!("Bitwarden item '{}' has empty notes", self.item_name);
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
22
crates/notsecrets/src/sources/file.rs
Normal file
22
crates/notsecrets/src/sources/file.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use crate::AgeKeySource;
|
||||
|
||||
pub struct FileSource {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl FileSource {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
Self { path }
|
||||
}
|
||||
}
|
||||
|
||||
impl AgeKeySource for FileSource {
|
||||
fn name(&self) -> &str { "file" }
|
||||
|
||||
fn retrieve(&self) -> Result<String> {
|
||||
std::fs::read_to_string(&self.path)
|
||||
.map_err(|e| anyhow::anyhow!("cannot read key file {}: {e}", self.path.display()))
|
||||
}
|
||||
}
|
||||
7
crates/notsecrets/src/sources/mod.rs
Normal file
7
crates/notsecrets/src/sources/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod bitwarden;
|
||||
pub mod file;
|
||||
pub mod prompt;
|
||||
|
||||
pub use bitwarden::BitwardenSource;
|
||||
pub use file::FileSource;
|
||||
pub use prompt::PromptSource;
|
||||
17
crates/notsecrets/src/sources/prompt.rs
Normal file
17
crates/notsecrets/src/sources/prompt.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use crate::AgeKeySource;
|
||||
|
||||
pub struct PromptSource;
|
||||
|
||||
impl AgeKeySource for PromptSource {
|
||||
fn name(&self) -> &str { "prompt" }
|
||||
|
||||
fn retrieve(&self) -> Result<String> {
|
||||
let key = rpassword::prompt_password("Paste your age private key: ")
|
||||
.map_err(|e| anyhow::anyhow!("could not read age key from prompt: {e}"))?;
|
||||
if key.trim().is_empty() {
|
||||
anyhow::bail!("empty age key entered");
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user