- Disappearing messages: ttlSecs param on enqueue/batchEnqueue RPCs, expires_at column (migration 007), server GC deletes expired messages, /disappear command with human-friendly duration parsing (30m, 1h, 1d) - Group info: /group-info shows type, members, MLS epoch; /rename renames conversations; /members resolves usernames via resolveIdentity - Account deletion: deleteAccount @23 RPC with transactional purge of all user data (deliveries, keys, channels), session invalidation, KT log preserved for auditability; /delete-account with confirmation - Added epoch() accessor to GroupMember, enqueue_with_ttl client helper All 35 server + 71 core + 14 E2E tests pass.
64 lines
2.0 KiB
Rust
64 lines
2.0 KiB
Rust
use capnp::capability::Promise;
|
|
use quicproquo_proto::node_capnp::node_service;
|
|
|
|
use crate::auth::{coded_error, require_identity, validate_auth_context};
|
|
use crate::error_codes::*;
|
|
|
|
use super::NodeServiceImpl;
|
|
|
|
impl NodeServiceImpl {
|
|
pub fn handle_delete_account(
|
|
&mut self,
|
|
params: node_service::DeleteAccountParams,
|
|
mut results: node_service::DeleteAccountResults,
|
|
) -> Promise<(), capnp::Error> {
|
|
let p = match params.get() {
|
|
Ok(p) => p,
|
|
Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
|
|
};
|
|
|
|
// Validate auth and require an identity-bound session.
|
|
let auth_ctx = match validate_auth_context(&self.auth_cfg, &self.sessions, p.get_auth()) {
|
|
Ok(ctx) => ctx,
|
|
Err(e) => return Promise::err(e),
|
|
};
|
|
|
|
let identity_key = match require_identity(&auth_ctx) {
|
|
Ok(ik) => ik.to_vec(),
|
|
Err(e) => return Promise::err(e),
|
|
};
|
|
|
|
let identity_prefix = crate::auth::fmt_hex(&identity_key[..8.min(identity_key.len())]);
|
|
|
|
// Delete account data from the store.
|
|
if let Err(e) = self.store.delete_account(&identity_key) {
|
|
tracing::error!(identity = %identity_prefix, error = %e, "account deletion failed");
|
|
return Promise::err(coded_error(
|
|
E028_ACCOUNT_DELETION_FAILED,
|
|
format!("account deletion failed: {e}"),
|
|
));
|
|
}
|
|
|
|
// Invalidate all sessions for this identity.
|
|
let tokens_to_remove: Vec<Vec<u8>> = self
|
|
.sessions
|
|
.iter()
|
|
.filter(|entry| entry.value().identity_key == identity_key)
|
|
.map(|entry| entry.key().clone())
|
|
.collect();
|
|
|
|
for token in &tokens_to_remove {
|
|
self.sessions.remove(token);
|
|
}
|
|
|
|
tracing::info!(
|
|
identity = %identity_prefix,
|
|
sessions_invalidated = tokens_to_remove.len(),
|
|
"audit: account deleted"
|
|
);
|
|
|
|
results.get().set_success(true);
|
|
Promise::ok(())
|
|
}
|
|
}
|