Compare commits
1 Commits
feature/v0
...
feature/v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91ceea0ed |
@@ -24,10 +24,7 @@ under `src/commands/`. Each source file has one job.
|
|||||||
- **`src/main.rs`** (`main.rs:1-492`) — clap surface and the flat dispatcher.
|
- **`src/main.rs`** (`main.rs:1-492`) — clap surface and the flat dispatcher.
|
||||||
Owns the top-level `Cli` / `Commands` enum and every subcommand enum
|
Owns the top-level `Cli` / `Commands` enum and every subcommand enum
|
||||||
(`AddKind`, `TrashAction`, `SettingsAction`, `BackupAction`, `ImportAction`,
|
(`AddKind`, `TrashAction`, `SettingsAction`, `BackupAction`, `ImportAction`,
|
||||||
`DeviceAction`, `RecoveryQrCmd`), plus the org clap surface `OrgCommands`
|
`DeviceAction`, `RecoveryQrCmd`). `main()` is a single `match` that
|
||||||
(`main.rs:448`) and `OrgAddKind` (`main.rs:556`) — the latter's Card / Key /
|
|
||||||
Document / Totp variants carry `--collection` and the `--*-stdin` secret flags.
|
|
||||||
`main()` is a single `match` that
|
|
||||||
delegates each variant to `commands::<verb>::cmd_<verb>(...)`. Also owns the
|
delegates each variant to `commands::<verb>::cmd_<verb>(...)`. Also owns the
|
||||||
three test-only env-var hooks (`test_passphrase_override`,
|
three test-only env-var hooks (`test_passphrase_override`,
|
||||||
`test_item_secret_override`, `test_backup_passphrase_override`) — each is
|
`test_item_secret_override`, `test_backup_passphrase_override`) — each is
|
||||||
@@ -97,14 +94,7 @@ under `src/commands/`. Each source file has one job.
|
|||||||
(`items/<collection-slug>/<id>.enc` — the leading slug is what the pre-receive
|
(`items/<collection-slug>/<id>.enc` — the leading slug is what the pre-receive
|
||||||
hook authorizes against, never decrypting), fingerprint-based member matching
|
hook authorizes against, never decrypting), fingerprint-based member matching
|
||||||
(`relicario_core::fingerprint`, tolerant of OpenSSH whitespace/comment
|
(`relicario_core::fingerprint`, tolerant of OpenSSH whitespace/comment
|
||||||
differences), `atomic_write`, and `org_git_run`. As of v0.8.1 it also owns
|
differences), `atomic_write`, and `org_git_run`. Note `org_git_run` runs
|
||||||
**collection-scoped attachment storage** — `attachment_path` /
|
|
||||||
`save_attachment` / `load_attachment` / `remove_item_attachments`
|
|
||||||
(`org_session.rs:125-157`) at layout
|
|
||||||
`attachments/<collection-slug>/<item-id>/<att-id>.enc` (the same leading slug
|
|
||||||
the pre-receive hook authorizes against as for `item_path`), capped
|
|
||||||
per-attachment by `DEFAULT_ORG_ATTACHMENT_MAX_BYTES` (10 MiB,
|
|
||||||
`org_session.rs:20`). Note `org_git_run` runs
|
|
||||||
**bare git** — unlike `helpers::git_run` it does NOT inject
|
**bare git** — unlike `helpers::git_run` it does NOT inject
|
||||||
`commit.gpgsign=false`, because org commits MUST be signed (the hook verifies
|
`commit.gpgsign=false`, because org commits MUST be signed (the hook verifies
|
||||||
every commit's signature); signing config is established by
|
every commit's signature); signing config is established by
|
||||||
@@ -121,38 +111,19 @@ under `src/commands/`. Each source file has one job.
|
|||||||
concurrent-rotation abort), `transfer-ownership`, `delete-org`, `status` /
|
concurrent-rotation abort), `transfer-ownership`, `delete-org`, `status` /
|
||||||
`audit` (verified-signer attribution + `TAMPERED` flag).
|
`audit` (verified-signer attribution + `TAMPERED` flag).
|
||||||
|
|
||||||
*Item CRUD (7):* full item-type parity with the personal vault (v0.8.1).
|
*Item CRUD (7):* `org add` creates typed items via `OrgAddKind`
|
||||||
`org add` creates **all seven types** (Login / SecureNote / Identity / Card /
|
(`commands/org.rs:749`) — **Login / SecureNote / Identity only**; Card /
|
||||||
Key / Document / Totp) via `OrgAddKind` (`commands/org.rs:751`); each arm
|
SshKey / Document / Totp creation is a deferred follow-up. `get` / `list` can
|
||||||
delegates to the shared `item_build::build_*` builders through `build_org_item`
|
display any item type if present. `org get <query> [--show]` masks secrets
|
||||||
(`commands/org.rs:799`), and `run_add` (`commands/org.rs:823`) sets tags
|
unless `--show`; `org list [--trashed]` filters by the caller's collection
|
||||||
post-build. Document is special-cased in `run_add` (`commands/org.rs:839`): its
|
grants; `org edit <query>` is flag-driven (blank flags keep current values);
|
||||||
builder also yields an `EncryptedAttachment` that is written via
|
`org rm` soft-deletes, `org restore` undoes, `org purge` permanently removes
|
||||||
`save_attachment` and git-staged before the signed commit. Single-line secrets
|
the encrypted blob. All item ops are collection-scoped and grant-enforced. The
|
||||||
(card number/CVV/PIN, TOTP secret, login password) accept a `--*-stdin` flag;
|
audit trail emits `item-create` / `item-update` / `item-delete` /
|
||||||
multiline secrets (Key material, SecureNote body) read stdin to EOF — the same
|
`item-restore` / `item-purge`.
|
||||||
`resolve_secret_line` / `resolve_secret_multiline` convention as personal `add`
|
|
||||||
(`commands/item_build.rs`).
|
|
||||||
|
|
||||||
`org edit <query>` (`run_edit`, `commands/org.rs:1004`) is **interactive
|
Deferred: Card / SshKey / Document / Totp `org add` / `edit` parity;
|
||||||
per-type** as of v0.8.1 (it was flag-driven before): it prompts Title, then
|
extension org reads and writes (Dev-D).
|
||||||
dispatches on `&mut item.core` to the shared `item_build::edit_*` helpers
|
|
||||||
("blank keeps current", field-history capture via `push_history`), mirroring
|
|
||||||
personal `cmd_edit`. `--totp-qr` sets a Login TOTP from a QR image; `--file`
|
|
||||||
replaces a Document's primary attachment (`commands/org.rs:1039`, rejected for
|
|
||||||
non-Document items at `commands/org.rs:1018`). The edit commit carries
|
|
||||||
`Relicario-Action: item-update`.
|
|
||||||
|
|
||||||
`org get <query> [--show]` masks every secret unless `--show`; `org list
|
|
||||||
[--trashed]` filters by the caller's collection grants; `org rm` soft-deletes,
|
|
||||||
`org restore` undoes, `org purge` (`run_purge`, `commands/org.rs:1164`)
|
|
||||||
permanently removes the encrypted blob **and** the item's attachment directory
|
|
||||||
(`remove_item_attachments`, `commands/org.rs:1173`). All item ops are
|
|
||||||
collection-scoped and grant-enforced (`filter_for_member` over the manifest +
|
|
||||||
`ensure_grant` before any load/mutate). The audit trail emits `item-create` /
|
|
||||||
`item-update` / `item-delete` / `item-restore` / `item-purge`.
|
|
||||||
|
|
||||||
Deferred: extension org reads and writes (Plan B-2 / phase 2).
|
|
||||||
|
|
||||||
- **`src/helpers.rs`** (`helpers.rs:1-101`) — pure, no-state plumbing:
|
- **`src/helpers.rs`** (`helpers.rs:1-101`) — pure, no-state plumbing:
|
||||||
`find_vault_dir_from` (`helpers.rs:14-28`) walks up parent directories
|
`find_vault_dir_from` (`helpers.rs:14-28`) walks up parent directories
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! (`commands/org.rs`). Centralizing it keeps the two surfaces from drifting.
|
//! (`commands/org.rs`). Centralizing it keeps the two surfaces from drifting.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -255,23 +255,39 @@ pub(crate) fn build_totp(
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a file and encrypt it as an attachment under `key`, deriving its display
|
||||||
|
/// metadata. The plaintext is held in a `Zeroizing` buffer so it is wiped after
|
||||||
|
/// encryption. Returns the encrypted blob plus (filename, mime_type, size).
|
||||||
|
pub(crate) fn encrypt_document_file(
|
||||||
|
path: &Path,
|
||||||
|
key: &Zeroizing<[u8; 32]>,
|
||||||
|
max_bytes: u64,
|
||||||
|
) -> Result<(EncryptedAttachment, String, String, u64)> {
|
||||||
|
use relicario_core::encrypt_attachment;
|
||||||
|
let bytes = Zeroizing::new(
|
||||||
|
std::fs::read(path).with_context(|| format!("failed to read {}", path.display()))?,
|
||||||
|
);
|
||||||
|
let enc = encrypt_attachment(&bytes, key, max_bytes)?;
|
||||||
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", path.display()))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
let mime_type = crate::parse::guess_mime(&filename);
|
||||||
|
Ok((enc, filename, mime_type, bytes.len() as u64))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build_document(
|
pub(crate) fn build_document(
|
||||||
title: String, file: PathBuf, key: &Zeroizing<[u8; 32]>, max_bytes: u64,
|
title: String, file: PathBuf, key: &Zeroizing<[u8; 32]>, max_bytes: u64,
|
||||||
) -> Result<(Item, EncryptedAttachment)> {
|
) -> Result<(Item, EncryptedAttachment)> {
|
||||||
use relicario_core::item_types::DocumentCore;
|
use relicario_core::item_types::DocumentCore;
|
||||||
use relicario_core::{encrypt_attachment, AttachmentRef};
|
use relicario_core::AttachmentRef;
|
||||||
let bytes = std::fs::read(&file).with_context(|| format!("failed to read {}", file.display()))?;
|
let (enc, filename, mime_type, size) = encrypt_document_file(&file, key, max_bytes)?;
|
||||||
let enc = encrypt_attachment(&bytes, key, max_bytes)?;
|
|
||||||
let filename = file.file_name()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", file.display()))?
|
|
||||||
.to_string_lossy().into_owned();
|
|
||||||
let mime_type = crate::parse::guess_mime(&filename);
|
|
||||||
let primary_attachment = enc.id.clone();
|
|
||||||
let mut item = Item::new(title, ItemCore::Document(DocumentCore {
|
let mut item = Item::new(title, ItemCore::Document(DocumentCore {
|
||||||
filename: filename.clone(), mime_type: mime_type.clone(), primary_attachment: primary_attachment.clone(),
|
filename: filename.clone(), mime_type: mime_type.clone(), primary_attachment: enc.id.clone(),
|
||||||
}));
|
}));
|
||||||
item.attachments.push(AttachmentRef {
|
item.attachments.push(AttachmentRef {
|
||||||
id: primary_attachment, filename, mime_type, size: bytes.len() as u64, created: item.created,
|
id: enc.id.clone(), filename, mime_type, size, created: item.created,
|
||||||
});
|
});
|
||||||
Ok((item, enc))
|
Ok((item, enc))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1038,25 +1038,18 @@ pub fn run_edit(dir: &Path, query: &str, totp_qr: Option<std::path::PathBuf>, fi
|
|||||||
ItemCore::Key(k) => ib::edit_key(k, history)?,
|
ItemCore::Key(k) => ib::edit_key(k, history)?,
|
||||||
ItemCore::Document(d) => {
|
ItemCore::Document(d) => {
|
||||||
if let Some(path) = &file {
|
if let Some(path) = &file {
|
||||||
let bytes = std::fs::read(path)
|
let (enc, filename, mime_type, size) = ib::encrypt_document_file(
|
||||||
.with_context(|| format!("read {}", path.display()))?;
|
path, vault.key(), crate::org_session::DEFAULT_ORG_ATTACHMENT_MAX_BYTES)?;
|
||||||
let enc = relicario_core::encrypt_attachment(
|
|
||||||
&bytes, vault.key(), crate::org_session::DEFAULT_ORG_ATTACHMENT_MAX_BYTES)?;
|
|
||||||
vault.remove_item_attachments(&collection, &id)?;
|
vault.remove_item_attachments(&collection, &id)?;
|
||||||
let rel = vault.save_attachment(&collection, &id, &enc)?;
|
let rel = vault.save_attachment(&collection, &id, &enc)?;
|
||||||
let filename = path
|
|
||||||
.file_name()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", path.display()))?
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
d.mime_type = crate::parse::guess_mime(&filename);
|
|
||||||
d.primary_attachment = enc.id.clone();
|
|
||||||
d.filename = filename.clone();
|
d.filename = filename.clone();
|
||||||
|
d.mime_type = mime_type.clone();
|
||||||
|
d.primary_attachment = enc.id.clone();
|
||||||
new_doc_attachments = Some(vec![relicario_core::AttachmentRef {
|
new_doc_attachments = Some(vec![relicario_core::AttachmentRef {
|
||||||
id: enc.id,
|
id: enc.id,
|
||||||
filename,
|
filename,
|
||||||
mime_type: d.mime_type.clone(),
|
mime_type,
|
||||||
size: bytes.len() as u64,
|
size,
|
||||||
created: now_unix(),
|
created: now_unix(),
|
||||||
}]);
|
}]);
|
||||||
doc_attachment_rel = Some(rel);
|
doc_attachment_rel = Some(rel);
|
||||||
|
|||||||
Reference in New Issue
Block a user