chore: rename quicproquo → quicprochat in Rust workspace
Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
This commit is contained in:
119
crates/quicprochat-sdk/src/recovery.rs
Normal file
119
crates/quicprochat-sdk/src/recovery.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Account recovery — setup, upload, and restore via recovery codes.
|
||||
//!
|
||||
//! Wraps `quicprochat_core::recovery` and the v2 RPC recovery service.
|
||||
|
||||
use bytes::Bytes;
|
||||
use prost::Message;
|
||||
use quicprochat_core::recovery::{
|
||||
generate_recovery_codes, recover_from_bundle, recovery_token_hash, RecoveryBundle,
|
||||
};
|
||||
use quicprochat_proto::{method_ids, qpc::v1};
|
||||
use quicprochat_rpc::client::RpcClient;
|
||||
|
||||
use crate::error::SdkError;
|
||||
|
||||
/// Set up account recovery: generate codes, encrypt bundles, upload to server.
|
||||
///
|
||||
/// Returns the recovery codes (display to user once, never store).
|
||||
pub async fn setup_recovery(
|
||||
rpc: &RpcClient,
|
||||
identity_seed: &[u8; 32],
|
||||
conversation_ids: &[Vec<u8>],
|
||||
) -> Result<Vec<String>, SdkError> {
|
||||
let setup = generate_recovery_codes(identity_seed, conversation_ids)
|
||||
.map_err(|e| SdkError::Crypto(format!("recovery code generation: {e}")))?;
|
||||
|
||||
// Upload each encrypted bundle to the server.
|
||||
for bundle in &setup.bundles {
|
||||
let bundle_bytes = bincode::serialize(bundle)
|
||||
.map_err(|e| SdkError::Crypto(format!("serialize recovery bundle: {e}")))?;
|
||||
|
||||
let req = v1::StoreRecoveryBundleRequest {
|
||||
token_hash: bundle.token_hash.clone(),
|
||||
bundle: bundle_bytes,
|
||||
ttl_secs: 0, // Use server default (90 days).
|
||||
};
|
||||
|
||||
let resp_bytes = rpc
|
||||
.call(
|
||||
method_ids::STORE_RECOVERY_BUNDLE,
|
||||
Bytes::from(req.encode_to_vec()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let resp = v1::StoreRecoveryBundleResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode store_recovery response: {e}")))?;
|
||||
|
||||
if !resp.success {
|
||||
return Err(SdkError::Crypto(
|
||||
"server rejected recovery bundle upload".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(setup.codes)
|
||||
}
|
||||
|
||||
/// Recover an account from a recovery code.
|
||||
///
|
||||
/// Fetches the encrypted bundle from the server, decrypts it with the code,
|
||||
/// and returns the identity seed and conversation IDs.
|
||||
pub async fn recover_account(
|
||||
rpc: &RpcClient,
|
||||
code: &str,
|
||||
) -> Result<(/* identity_seed */ [u8; 32], /* conversation_ids */ Vec<Vec<u8>>), SdkError> {
|
||||
// Compute the token hash for server-side lookup.
|
||||
let token_hash = recovery_token_hash(code);
|
||||
|
||||
let req = v1::FetchRecoveryBundleRequest {
|
||||
token_hash: token_hash.clone(),
|
||||
};
|
||||
|
||||
let resp_bytes = rpc
|
||||
.call(
|
||||
method_ids::FETCH_RECOVERY_BUNDLE,
|
||||
Bytes::from(req.encode_to_vec()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let resp = v1::FetchRecoveryBundleResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode fetch_recovery response: {e}")))?;
|
||||
|
||||
if resp.bundle.is_empty() {
|
||||
return Err(SdkError::Crypto(
|
||||
"no recovery bundle found for this code".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Deserialize the bundle.
|
||||
let bundle: RecoveryBundle = bincode::deserialize(&resp.bundle)
|
||||
.map_err(|e| SdkError::Crypto(format!("deserialize recovery bundle: {e}")))?;
|
||||
|
||||
// Decrypt with the code.
|
||||
let payload = recover_from_bundle(code, &bundle)
|
||||
.map_err(|e| SdkError::Crypto(format!("recovery decryption failed: {e}")))?;
|
||||
|
||||
Ok((payload.identity_seed, payload.conversation_ids))
|
||||
}
|
||||
|
||||
/// Delete all recovery bundles for the given codes (e.g. after refresh).
|
||||
pub async fn delete_recovery_bundles(
|
||||
rpc: &RpcClient,
|
||||
codes: &[String],
|
||||
) -> Result<(), SdkError> {
|
||||
for code in codes {
|
||||
let token_hash = recovery_token_hash(code);
|
||||
let req = v1::DeleteRecoveryBundleRequest { token_hash };
|
||||
|
||||
let resp_bytes = rpc
|
||||
.call(
|
||||
method_ids::DELETE_RECOVERY_BUNDLE,
|
||||
Bytes::from(req.encode_to_vec()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _resp = v1::DeleteRecoveryBundleResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode delete_recovery response: {e}")))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user