feat(v3): migrate workspace to API, Tauri desktop, and v3 crates; remove legacy MCP stack
Some checks failed
Secrets v3 CI / 检查 (push) Has been cancelled

- Add apps/api, desktop Tauri shell, domain/application/crypto/device-auth/infrastructure-db
- Replace desktop-daemon vault integration; drop secrets-core and secrets-mcp*
- Ignore apps/desktop/dist and generated Tauri icons; document icon/dist steps in AGENTS.md
- Apply rustfmt; fix clippy (collapsible_if, HTTP method as str)
This commit is contained in:
agent
2026-04-13 08:49:57 +08:00
parent cb5865b958
commit 0374899dab
130 changed files with 20447 additions and 21577 deletions

16
crates/domain/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "secrets-domain"
version = "0.1.0"
edition.workspace = true
[lib]
name = "secrets_domain"
path = "src/lib.rs"
[dependencies]
argon2 = "0.5.3"
chrono.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
uuid.workspace = true

68
crates/domain/src/auth.rs Normal file
View File

@@ -0,0 +1,68 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub email: Option<String>,
pub name: String,
pub avatar_url: Option<String>,
pub key_salt: Option<Vec<u8>>,
pub key_check: Option<Vec<u8>>,
pub key_params: Option<Value>,
pub key_version: i64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Device {
pub id: Uuid,
pub user_id: Uuid,
pub display_name: String,
pub platform: String,
pub client_version: String,
pub device_fingerprint: String,
pub created_at: DateTime<Utc>,
pub last_seen_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceLoginToken {
pub id: Uuid,
pub device_id: Uuid,
pub token_hash: String,
pub created_at: DateTime<Utc>,
pub last_seen_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LoginMethod {
GoogleOauth,
DeviceToken,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LoginResult {
Success,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientLoginEvent {
pub id: i64,
pub user_id: Uuid,
pub device_id: Uuid,
pub device_name: String,
pub platform: String,
pub client_version: String,
pub ip_addr: Option<String>,
pub forwarded_ip: Option<String>,
pub login_method: LoginMethod,
pub login_result: LoginResult,
pub created_at: DateTime<Utc>,
}

138
crates/domain/src/cipher.rs Normal file
View File

@@ -0,0 +1,138 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CipherType {
Login,
ApiKey,
SecureNote,
SshKey,
Identity,
Card,
}
impl CipherType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Login => "login",
Self::ApiKey => "api_key",
Self::SecureNote => "secure_note",
Self::SshKey => "ssh_key",
Self::Identity => "identity",
Self::Card => "card",
}
}
pub fn parse(input: &str) -> Self {
match input {
"login" => Self::Login,
"api_key" => Self::ApiKey,
"secure_note" => Self::SecureNote,
"ssh_key" => Self::SshKey,
"identity" => Self::Identity,
"card" => Self::Card,
_ => Self::SecureNote,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CustomField {
pub name: String,
pub value: Value,
#[serde(default)]
pub sensitive: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct LoginPayload {
#[serde(default)]
pub username: Option<String>,
#[serde(default)]
pub uris: Vec<String>,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub totp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ApiKeyPayload {
#[serde(default)]
pub client_id: Option<String>,
#[serde(default)]
pub secret: Option<String>,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub host: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct SecureNotePayload {
#[serde(default)]
pub text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct SshKeyPayload {
#[serde(default)]
pub username: Option<String>,
#[serde(default)]
pub host: Option<String>,
#[serde(default)]
pub port: Option<u16>,
#[serde(default)]
pub private_key: Option<String>,
#[serde(default)]
pub passphrase: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ItemPayload {
Login(LoginPayload),
ApiKey(ApiKeyPayload),
SecureNote(SecureNotePayload),
SshKey(SshKeyPayload),
}
impl Default for ItemPayload {
fn default() -> Self {
Self::SecureNote(SecureNotePayload::default())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CipherView {
pub id: Uuid,
pub cipher_type: CipherType,
pub name: String,
pub folder: String,
#[serde(default)]
pub notes: Option<String>,
#[serde(default)]
pub custom_fields: Vec<CustomField>,
#[serde(default)]
pub deleted_at: Option<DateTime<Utc>>,
pub revision_date: DateTime<Utc>,
pub payload: ItemPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Cipher {
pub id: Uuid,
pub user_id: Uuid,
pub object_kind: String,
pub cipher_type: CipherType,
pub revision: i64,
pub cipher_version: i32,
pub ciphertext: Vec<u8>,
pub content_hash: String,
pub deleted_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

View File

@@ -0,0 +1,15 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DomainError {
#[error("resource not found")]
NotFound,
#[error("resource already exists")]
Conflict,
#[error("validation failed: {0}")]
Validation(String),
#[error("authentication failed")]
AuthenticationFailed,
#[error("decryption failed")]
DecryptionFailed,
}

37
crates/domain/src/kdf.rs Normal file
View File

@@ -0,0 +1,37 @@
use argon2::{Algorithm, Argon2, Params, Version};
use serde::{Deserialize, Serialize};
use crate::DomainError;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum KdfType {
Argon2id,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct KdfConfig {
pub kdf_type: KdfType,
pub memory_kib: u32,
pub iterations: u32,
pub parallelism: u32,
}
impl Default for KdfConfig {
fn default() -> Self {
Self {
kdf_type: KdfType::Argon2id,
memory_kib: 64 * 1024,
iterations: 3,
parallelism: 4,
}
}
}
impl KdfConfig {
pub fn build_argon2(&self) -> Result<Argon2<'static>, DomainError> {
let params = Params::new(self.memory_kib, self.iterations, self.parallelism, Some(32))
.map_err(|err| DomainError::Validation(err.to_string()))?;
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params))
}
}

19
crates/domain/src/lib.rs Normal file
View File

@@ -0,0 +1,19 @@
pub mod auth;
pub mod cipher;
pub mod error;
pub mod kdf;
pub mod sync;
pub mod vault_object;
pub use auth::{ClientLoginEvent, Device, DeviceLoginToken, LoginMethod, LoginResult, User};
pub use cipher::{
ApiKeyPayload, Cipher, CipherType, CipherView, CustomField, ItemPayload, LoginPayload,
SecureNotePayload, SshKeyPayload,
};
pub use error::DomainError;
pub use kdf::{KdfConfig, KdfType};
pub use sync::{
SyncAcceptedChange, SyncConflict, SyncPullRequest, SyncPullResponse, SyncPushRequest,
SyncPushResponse,
};
pub use vault_object::{VaultObjectChange, VaultObjectEnvelope, VaultObjectKind, VaultTombstone};

47
crates/domain/src/sync.rs Normal file
View File

@@ -0,0 +1,47 @@
use serde::{Deserialize, Serialize};
use crate::vault_object::{VaultObjectChange, VaultObjectEnvelope, VaultTombstone};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncPullRequest {
pub cursor: Option<i64>,
pub limit: Option<i64>,
#[serde(default)]
pub include_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncPullResponse {
pub server_revision: i64,
pub next_cursor: i64,
pub has_more: bool,
pub objects: Vec<VaultObjectEnvelope>,
pub tombstones: Vec<VaultTombstone>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncPushRequest {
pub changes: Vec<VaultObjectChange>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncAcceptedChange {
pub change_id: uuid::Uuid,
pub object_id: uuid::Uuid,
pub revision: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncConflict {
pub change_id: uuid::Uuid,
pub object_id: uuid::Uuid,
pub reason: String,
pub server_object: Option<VaultObjectEnvelope>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncPushResponse {
pub server_revision: i64,
pub accepted: Vec<SyncAcceptedChange>,
pub conflicts: Vec<SyncConflict>,
}

View File

@@ -0,0 +1,48 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VaultObjectKind {
Cipher,
}
impl VaultObjectKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Cipher => "cipher",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VaultObjectEnvelope {
pub object_id: Uuid,
pub object_kind: VaultObjectKind,
pub revision: i64,
pub cipher_version: i32,
pub ciphertext: Vec<u8>,
pub content_hash: String,
pub deleted_at: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VaultObjectChange {
pub change_id: Uuid,
pub object_id: Uuid,
pub object_kind: VaultObjectKind,
pub operation: String,
pub base_revision: Option<i64>,
pub cipher_version: Option<i32>,
pub ciphertext: Option<Vec<u8>>,
pub content_hash: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VaultTombstone {
pub object_id: Uuid,
pub revision: i64,
pub deleted_at: DateTime<Utc>,
}