From 82feb49ab4e66926f8050457917be1b55e9cff73 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 20 Jun 2026 18:31:29 -0400 Subject: [PATCH] feat(cli/org): org add parity for Card/Key/Totp via shared builders --- crates/relicario-cli/src/commands/org.rs | 88 ++++++++++++++---------- crates/relicario-cli/src/main.rs | 62 +++++++++++++++-- 2 files changed, 110 insertions(+), 40 deletions(-) diff --git a/crates/relicario-cli/src/commands/org.rs b/crates/relicario-cli/src/commands/org.rs index dbb8fa0..33dd3ae 100644 --- a/crates/relicario-cli/src/commands/org.rs +++ b/crates/relicario-cli/src/commands/org.rs @@ -6,12 +6,13 @@ use std::path::Path; use anyhow::{Context, Result}; use relicario_core::{ generate_org_key, wrap_org_key, - CollectionDef, Item, ItemCore, MemberId, OrgCollections, OrgManifest, OrgMembers, OrgMeta, + CollectionDef, Item, MemberId, OrgCollections, OrgManifest, OrgMembers, OrgMeta, OrgRole, OrgMember, encrypt_org_manifest, }; use crate::org_session::atomic_write; +use crate::commands::item_build as ib; pub fn run_init(dir: &Path, name: &str) -> Result<()> { // Create directory structure @@ -745,17 +746,20 @@ pub fn run_audit( Ok(()) } -/// Item kinds `org add` supports without interactive prompts. +/// Item kinds `org add` supports. Secrets resolve via `--*-stdin` flags or an +/// interactive prompt inside the shared `item_build` builders. pub enum OrgAddKind { Login { title: String, username: Option, url: Option, password: Option, + password_stdin: bool, }, SecureNote { title: String, - body: String, + body: Option, + body_stdin: bool, }, Identity { title: String, @@ -763,43 +767,56 @@ pub enum OrgAddKind { email: Option, phone: Option, }, + Card { + title: String, + holder: Option, + expiry: Option, + kind: String, + number_stdin: bool, + cvv_stdin: bool, + pin_stdin: bool, + }, + Key { + title: String, + label: Option, + algorithm: Option, + public_key: Option, + material_stdin: bool, + }, + Totp { + title: String, + issuer: Option, + label: Option, + secret: Option, + secret_stdin: bool, + period: u32, + digits: u8, + algorithm: String, + }, + // Document is added later by Dev-C. } -fn build_org_item(kind: OrgAddKind, tags: Vec) -> Result { - use relicario_core::item_types::{IdentityCore, LoginCore, SecureNoteCore}; - use zeroize::Zeroizing; - - let mut item = match kind { - OrgAddKind::Login { title, username, url, password } => { - let parsed_url = match url { - Some(s) => Some(url::Url::parse(&s).with_context(|| format!("invalid URL: {s}"))?), - None => None, - }; - let password = password.map(Zeroizing::new); - Item::new(title, ItemCore::Login(LoginCore { - username, - password, - url: parsed_url, - totp: None, - })) +fn build_org_item(kind: OrgAddKind) -> Result { + match kind { + OrgAddKind::Login { title, username, url, password, password_stdin } => { + ib::build_login(title, username, url, password, password_stdin, false, None) } - OrgAddKind::SecureNote { title, body } => { - Item::new(title, ItemCore::SecureNote(SecureNoteCore { - body: Zeroizing::new(body), - })) + OrgAddKind::SecureNote { title, body, body_stdin } => { + ib::build_secure_note(title, body, body_stdin) } OrgAddKind::Identity { title, full_name, email, phone } => { - Item::new(title, ItemCore::Identity(IdentityCore { - full_name, - address: None, - phone, - email, - date_of_birth: None, - })) + ib::build_identity(title, full_name, email, phone, None) } - }; - item.tags = tags; - Ok(item) + OrgAddKind::Card { title, holder, expiry, kind, number_stdin, cvv_stdin, pin_stdin } => { + ib::build_card(title, holder, expiry, &kind, number_stdin, cvv_stdin, pin_stdin) + } + OrgAddKind::Key { title, label, algorithm, public_key, material_stdin } => { + ib::build_key(title, label, algorithm, public_key, material_stdin) + } + OrgAddKind::Totp { title, issuer, label, secret, secret_stdin, period, digits, algorithm } => { + ib::build_totp(title, issuer, label, secret, secret_stdin, period, digits, &algorithm) + } + } } pub fn run_add(dir: &Path, collection: &str, kind: OrgAddKind, tags: Vec) -> Result<()> { @@ -816,7 +833,8 @@ pub fn run_add(dir: &Path, collection: &str, kind: OrgAddKind, tags: Vec // …and the caller must hold a grant for it. UnlockedOrgVault::ensure_grant(&caller, collection)?; - let item = build_org_item(kind, tags)?; + let mut item = build_org_item(kind)?; + item.tags = tags; let item_rel = vault.save_item(collection, &item)?; // Upsert the manifest entry (collection slug stored plaintext inside the diff --git a/crates/relicario-cli/src/main.rs b/crates/relicario-cli/src/main.rs index e3f586a..29e0b87 100644 --- a/crates/relicario-cli/src/main.rs +++ b/crates/relicario-cli/src/main.rs @@ -566,13 +566,15 @@ pub(crate) enum OrgAddKind { #[arg(long)] url: Option, #[arg(long)] password: Option, #[arg(long, value_delimiter = ',')] tags: Vec, + #[arg(long)] password_stdin: bool, }, /// A secure note. SecureNote { #[arg(long)] collection: String, #[arg(long)] title: String, - #[arg(long)] body: String, + #[arg(long)] body: Option, #[arg(long, value_delimiter = ',')] tags: Vec, + #[arg(long)] body_stdin: bool, }, /// An identity record. Identity { @@ -583,6 +585,41 @@ pub(crate) enum OrgAddKind { #[arg(long)] phone: Option, #[arg(long, value_delimiter = ',')] tags: Vec, }, + /// A payment card (number / cvv / pin entered via --*-stdin or prompt). + Card { + #[arg(long)] collection: String, + #[arg(long)] title: String, + #[arg(long)] holder: Option, + #[arg(long)] expiry: Option, + #[arg(long, default_value = "credit")] kind: String, + #[arg(long, value_delimiter = ',')] tags: Vec, + #[arg(long)] number_stdin: bool, + #[arg(long)] cvv_stdin: bool, + #[arg(long)] pin_stdin: bool, + }, + /// A key / credential blob (material entered via --material-stdin or prompt). + Key { + #[arg(long)] collection: String, + #[arg(long)] title: String, + #[arg(long)] label: Option, + #[arg(long)] algorithm: Option, + #[arg(long)] public_key: Option, + #[arg(long, value_delimiter = ',')] tags: Vec, + #[arg(long)] material_stdin: bool, + }, + /// A TOTP authenticator (base32 secret via --secret or --secret-stdin). + Totp { + #[arg(long)] collection: String, + #[arg(long)] title: String, + #[arg(long)] issuer: Option, + #[arg(long)] label: Option, + #[arg(long)] secret: Option, + #[arg(long, default_value_t = 30)] period: u32, + #[arg(long, default_value_t = 6)] digits: u8, + #[arg(long, default_value = "sha1")] algorithm: String, + #[arg(long, value_delimiter = ',')] tags: Vec, + #[arg(long)] secret_stdin: bool, + }, } fn main() -> Result<()> { @@ -676,14 +713,14 @@ fn main() -> Result<()> { OrgCommands::Add { kind } => { let d = crate::org_session::org_dir(dir_path)?; let (collection, add_kind, tags) = match kind { - OrgAddKind::Login { collection, title, username, url, password, tags } => ( + OrgAddKind::Login { collection, title, username, url, password, tags, password_stdin } => ( collection, - commands::org::OrgAddKind::Login { title, username, url, password }, + commands::org::OrgAddKind::Login { title, username, url, password, password_stdin }, tags, ), - OrgAddKind::SecureNote { collection, title, body, tags } => ( + OrgAddKind::SecureNote { collection, title, body, tags, body_stdin } => ( collection, - commands::org::OrgAddKind::SecureNote { title, body }, + commands::org::OrgAddKind::SecureNote { title, body, body_stdin }, tags, ), OrgAddKind::Identity { collection, title, full_name, email, phone, tags } => ( @@ -691,6 +728,21 @@ fn main() -> Result<()> { commands::org::OrgAddKind::Identity { title, full_name, email, phone }, tags, ), + OrgAddKind::Card { collection, title, holder, expiry, kind, tags, number_stdin, cvv_stdin, pin_stdin } => ( + collection, + commands::org::OrgAddKind::Card { title, holder, expiry, kind, number_stdin, cvv_stdin, pin_stdin }, + tags, + ), + OrgAddKind::Key { collection, title, label, algorithm, public_key, tags, material_stdin } => ( + collection, + commands::org::OrgAddKind::Key { title, label, algorithm, public_key, material_stdin }, + tags, + ), + OrgAddKind::Totp { collection, title, issuer, label, secret, period, digits, algorithm, tags, secret_stdin } => ( + collection, + commands::org::OrgAddKind::Totp { title, issuer, label, secret, secret_stdin, period, digits, algorithm }, + tags, + ), }; commands::org::run_add(&d, &collection, add_kind, tags)?; }