feat(notsecrets): migrate sources to IdentitySource trait, add resolve_identities, remove legacy API

- Replace AgeKeySource+retrieve() with IdentitySource+load() returning Box<dyn Identity>
- Rewrite bitwarden/file/prompt sources to implement new trait
- Add resolve_identities() public API with partial-success semantics
- Delete _legacy.rs and remove legacy re-exports from lib.rs
- Update integration tests to use new API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Joseph O'Brien
2026-04-01 21:16:39 -04:00
parent e388d75cf1
commit f80ea02f07
7 changed files with 253 additions and 203 deletions

View File

@@ -1,19 +1,28 @@
use crate::_legacy::AgeKeySource;
use anyhow::Result;
use crate::error::AgeError;
use crate::identities::scrypt::ScryptIdentity;
use crate::identities::Identity;
use crate::sources::IdentitySource;
pub struct PromptSource;
impl AgeKeySource for PromptSource {
impl IdentitySource 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");
fn load(&self) -> Result<Box<dyn Identity>, AgeError> {
let passphrase = rpassword::prompt_password("Enter age passphrase: ").map_err(|e| {
AgeError::SourceError {
name: self.name().to_string(),
source: anyhow::anyhow!("could not read passphrase: {e}"),
}
})?;
if passphrase.trim().is_empty() {
return Err(AgeError::SourceError {
name: self.name().to_string(),
source: anyhow::anyhow!("empty passphrase entered"),
});
}
Ok(key)
Ok(Box::new(ScryptIdentity::new(passphrase)))
}
}