Files
quicproquo/crates/quicproquo-server/src/node_service/account_ops.rs
Christian Nennemann fd21ea625c feat: Sprint 6 — disappearing messages, group info, account deletion
- 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.
2026-03-04 00:39:05 +01:00

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(())
}
}