feat(cli/org): org add parity for Card/Key/Totp via shared builders

This commit is contained in:
adlee-was-taken
2026-06-20 18:31:29 -04:00
parent 07862b8d44
commit 82feb49ab4
2 changed files with 110 additions and 40 deletions

View File

@@ -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<String>,
url: Option<String>,
password: Option<String>,
password_stdin: bool,
},
SecureNote {
title: String,
body: String,
body: Option<String>,
body_stdin: bool,
},
Identity {
title: String,
@@ -763,43 +767,56 @@ pub enum OrgAddKind {
email: Option<String>,
phone: Option<String>,
},
Card {
title: String,
holder: Option<String>,
expiry: Option<String>,
kind: String,
number_stdin: bool,
cvv_stdin: bool,
pin_stdin: bool,
},
Key {
title: String,
label: Option<String>,
algorithm: Option<String>,
public_key: Option<String>,
material_stdin: bool,
},
Totp {
title: String,
issuer: Option<String>,
label: Option<String>,
secret: Option<String>,
secret_stdin: bool,
period: u32,
digits: u8,
algorithm: String,
},
// Document is added later by Dev-C.
}
fn build_org_item(kind: OrgAddKind, tags: Vec<String>) -> Result<Item> {
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<Item> {
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<String>) -> Result<()> {
@@ -816,7 +833,8 @@ pub fn run_add(dir: &Path, collection: &str, kind: OrgAddKind, tags: Vec<String>
// …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

View File

@@ -566,13 +566,15 @@ pub(crate) enum OrgAddKind {
#[arg(long)] url: Option<String>,
#[arg(long)] password: Option<String>,
#[arg(long, value_delimiter = ',')] tags: Vec<String>,
#[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<String>,
#[arg(long, value_delimiter = ',')] tags: Vec<String>,
#[arg(long)] body_stdin: bool,
},
/// An identity record.
Identity {
@@ -583,6 +585,41 @@ pub(crate) enum OrgAddKind {
#[arg(long)] phone: Option<String>,
#[arg(long, value_delimiter = ',')] tags: Vec<String>,
},
/// A payment card (number / cvv / pin entered via --*-stdin or prompt).
Card {
#[arg(long)] collection: String,
#[arg(long)] title: String,
#[arg(long)] holder: Option<String>,
#[arg(long)] expiry: Option<String>,
#[arg(long, default_value = "credit")] kind: String,
#[arg(long, value_delimiter = ',')] tags: Vec<String>,
#[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<String>,
#[arg(long)] algorithm: Option<String>,
#[arg(long)] public_key: Option<String>,
#[arg(long, value_delimiter = ',')] tags: Vec<String>,
#[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<String>,
#[arg(long)] label: Option<String>,
#[arg(long)] secret: Option<String>,
#[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<String>,
#[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)?;
}