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:
2026-03-04 00:39:05 +01:00
parent 3350d765e5
commit fd21ea625c
13 changed files with 563 additions and 38 deletions

View File

@@ -226,6 +226,16 @@ pub async fn enqueue(
client: &node_service::Client,
recipient_key: &[u8],
payload: &[u8],
) -> anyhow::Result<u64> {
enqueue_with_ttl(client, recipient_key, payload, None).await
}
/// Enqueue with an optional TTL (seconds). 0 or None means no expiry.
pub async fn enqueue_with_ttl(
client: &node_service::Client,
recipient_key: &[u8],
payload: &[u8],
ttl_secs: Option<u32>,
) -> anyhow::Result<u64> {
let client = client.clone();
let recipient_key = recipient_key.to_vec();
@@ -243,6 +253,9 @@ pub async fn enqueue(
p.set_payload(&payload);
p.set_channel_id(&[]);
p.set_version(1);
if let Some(ttl) = ttl_secs {
p.set_ttl_secs(ttl);
}
let mut auth = p.reborrow().init_auth();
set_auth(&mut auth)?;
}
@@ -831,6 +844,32 @@ pub async fn download_blob_chunk(
Ok((chunk, total_size, mime_type))
}
/// Delete the authenticated user's account on the server.
/// Requires an identity-bound session (OPAQUE login).
pub async fn delete_account(
client: &node_service::Client,
) -> anyhow::Result<bool> {
let mut req = client.delete_account_request();
{
let mut p = req.get();
let mut auth = p.reborrow().init_auth();
set_auth(&mut auth)?;
}
let resp = req
.send()
.promise
.await
.context("delete_account RPC failed")?;
let success = resp
.get()
.context("delete_account: bad response")?
.get_success();
Ok(success)
}
/// Return the current Unix timestamp in milliseconds.
pub fn current_timestamp_ms() -> u64 {
std::time::SystemTime::now()