feat(core): flesh out LoginCore with Zeroizing<password> and Url
Also enables zeroize's `serde` feature so Zeroizing<String> can round-trip through serde_json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2222,6 +2222,7 @@ version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||
|
||||
# Typed-item additions
|
||||
zeroize = { version = "1", features = ["zeroize_derive"] }
|
||||
zeroize = { version = "1", features = ["zeroize_derive", "serde"] }
|
||||
zxcvbn = { version = "3", default-features = false }
|
||||
bip39 = { version = "2", default-features = false, features = ["std"] }
|
||||
unicode-normalization = "0.1"
|
||||
|
||||
@@ -1,3 +1,63 @@
|
||||
//! Login item core: username, password (Zeroizing), URL, optional TOTP.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::item_types::TotpConfig;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct LoginCore {}
|
||||
pub struct LoginCore {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub username: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub password: Option<Zeroizing<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<Url>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub totp: Option<TotpConfig>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_login_round_trips() {
|
||||
let login = LoginCore::default();
|
||||
let json = serde_json::to_string(&login).unwrap();
|
||||
let parsed: LoginCore = serde_json::from_str(&json).unwrap();
|
||||
assert!(parsed.username.is_none());
|
||||
assert!(parsed.password.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_login_round_trips() {
|
||||
let login = LoginCore {
|
||||
username: Some("alice".into()),
|
||||
password: Some(Zeroizing::new("hunter2".into())),
|
||||
url: Some(Url::parse("https://github.com/login").unwrap()),
|
||||
totp: None,
|
||||
};
|
||||
let json = serde_json::to_string(&login).unwrap();
|
||||
let parsed: LoginCore = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.username.as_deref(), Some("alice"));
|
||||
assert_eq!(parsed.password.as_deref().map(String::as_str), Some("hunter2"));
|
||||
assert_eq!(parsed.url.as_ref().map(Url::as_str), Some("https://github.com/login"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_fields_dont_appear_in_json() {
|
||||
let login = LoginCore {
|
||||
username: Some("alice".into()),
|
||||
password: None,
|
||||
url: None,
|
||||
totp: None,
|
||||
};
|
||||
let json = serde_json::to_string(&login).unwrap();
|
||||
assert!(!json.contains("password"));
|
||||
assert!(!json.contains("url"));
|
||||
assert!(!json.contains("totp"));
|
||||
assert!(json.contains("alice"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user