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.
This commit is contained in:
63
crates/quicproquo-server/src/node_service/account_ops.rs
Normal file
63
crates/quicproquo-server/src/node_service/account_ops.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user