feat: implement account recovery with encrypted backup bundles
Add recovery code generation (8 codes per setup), Argon2id key derivation, ChaCha20-Poly1305 encrypted bundles, and server-side zero-knowledge storage. Each code independently recovers the account. Includes core crypto module, protobuf service (method IDs 750-752), server domain + handlers, SDK methods, SQL migration, and CLI commands (/recovery setup, /recovery restore).
This commit is contained in:
@@ -122,6 +122,18 @@ enum Cmd {
|
||||
#[command(subcommand)]
|
||||
action: DevicesCmd,
|
||||
},
|
||||
|
||||
/// Account recovery management.
|
||||
Recovery {
|
||||
#[command(subcommand)]
|
||||
action: RecoveryCmd,
|
||||
},
|
||||
|
||||
/// Offline outbox management.
|
||||
Outbox {
|
||||
#[command(subcommand)]
|
||||
action: OutboxCmd,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
@@ -163,6 +175,27 @@ enum DevicesCmd {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum RecoveryCmd {
|
||||
/// Generate recovery codes and upload encrypted bundles.
|
||||
Setup,
|
||||
/// Recover account from a recovery code.
|
||||
Restore {
|
||||
/// Recovery code (e.g. "A3B7K9").
|
||||
code: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum OutboxCmd {
|
||||
/// Show pending outbox entries.
|
||||
List,
|
||||
/// Retry sending all pending outbox entries.
|
||||
Retry,
|
||||
/// Clear permanently failed outbox entries.
|
||||
Clear,
|
||||
}
|
||||
|
||||
// ── Auto-server launch ───────────────────────────────────────────────────────
|
||||
|
||||
/// RAII guard that kills an auto-started server process on drop.
|
||||
@@ -481,6 +514,49 @@ async fn run(args: Args) -> anyhow::Result<()> {
|
||||
.await
|
||||
.context("device revoke failed")?;
|
||||
}
|
||||
|
||||
Cmd::Recovery {
|
||||
action: RecoveryCmd::Setup,
|
||||
} => {
|
||||
let mut client = connect_client(&args).await?;
|
||||
v2_commands::cmd_recovery_setup(&mut client)
|
||||
.await
|
||||
.context("recovery setup failed")?;
|
||||
}
|
||||
|
||||
Cmd::Recovery {
|
||||
action: RecoveryCmd::Restore { ref code },
|
||||
} => {
|
||||
let mut client = connect_client(&args).await?;
|
||||
v2_commands::cmd_recovery_restore(&mut client, code)
|
||||
.await
|
||||
.context("recovery restore failed")?;
|
||||
}
|
||||
|
||||
Cmd::Outbox {
|
||||
action: OutboxCmd::List,
|
||||
} => {
|
||||
let mut client = connect_client(&args).await?;
|
||||
v2_commands::cmd_outbox_list(&client)
|
||||
.context("outbox list failed")?;
|
||||
}
|
||||
|
||||
Cmd::Outbox {
|
||||
action: OutboxCmd::Retry,
|
||||
} => {
|
||||
let mut client = connect_client(&args).await?;
|
||||
v2_commands::cmd_outbox_retry(&mut client)
|
||||
.await
|
||||
.context("outbox retry failed")?;
|
||||
}
|
||||
|
||||
Cmd::Outbox {
|
||||
action: OutboxCmd::Clear,
|
||||
} => {
|
||||
let mut client = connect_client(&args).await?;
|
||||
v2_commands::cmd_outbox_clear(&client)
|
||||
.context("outbox clear failed")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user