From 4198ed1efb2878146353257ecbe01c1fb7e189c4 Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 28 Jan 2025 05:14:02 +0000 Subject: [PATCH] fix: store device token in IMAP METADATA on each connection APNS tokens never expire unless the user uninstalls the application. Because of this in most cases the token remains valid forever and chatmail server never removes the token even if it is unencrypted or the user has removed Delta Chat profile from the device but not the whole application. We want to modify chatmail servers to remember the last time the token was stored and remove them after some time. Before we do this, we need to modify the client to store the device token each time so the server knows which tokens are used and can update their timestamps. --- src/config.rs | 7 ++++++ src/context.rs | 1 + src/imap.rs | 67 +++++++++++++++++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8ac186be7..b0e319137 100644 --- a/src/config.rs +++ b/src/config.rs @@ -455,6 +455,13 @@ pub enum Config { /// If it has not changed, we do not store /// the device token again. DeviceToken, + + /// Device token encrypted with OpenPGP. + /// + /// We store encrypted token next to `device_token` + /// to avoid encrypting it differently and + /// storing the same token multiple times on the server. + EncryptedDeviceToken, } impl Config { diff --git a/src/context.rs b/src/context.rs index 60a3c60f3..6dc2d66e3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1778,6 +1778,7 @@ mod tests { "key_id", "webxdc_integration", "device_token", + "encrypted_device_token", ]; let t = TestContext::new().await; let info = t.get_info().await.unwrap(); diff --git a/src/imap.rs b/src/imap.rs index aac2bb944..dc1b531b4 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1589,15 +1589,15 @@ impl Session { }; if self.can_metadata() && self.can_push() { - let device_token_changed = - context.get_config(Config::DeviceToken).await?.as_ref() != Some(&device_token); + let old_encrypted_device_token = + context.get_config(Config::EncryptedDeviceToken).await?; + // Whether we need to update encrypted device token. + let device_token_changed = old_encrypted_device_token.is_none() + || context.get_config(Config::DeviceToken).await?.as_ref() != Some(&device_token); + + let new_encrypted_device_token; if device_token_changed { - let folder = context - .get_config(Config::ConfiguredInboxFolder) - .await? - .context("INBOX is not configured")?; - let encrypted_device_token = encrypt_device_token(&device_token) .context("Failed to encrypt device token")?; @@ -1606,22 +1606,23 @@ impl Session { // . let encrypted_device_token_len = encrypted_device_token.len(); - if encrypted_device_token_len <= 4096 { - self.run_command_and_check_ok(&format_setmetadata( - &folder, - &encrypted_device_token, - )) - .await - .context("SETMETADATA command failed")?; + // Store device token saved on the server + // to prevent storing duplicate tokens. + // The server cannot deduplicate on its own + // because encryption gives a different + // result each time. + context + .set_config_internal(Config::DeviceToken, Some(&device_token)) + .await?; + context + .set_config_internal( + Config::EncryptedDeviceToken, + Some(&encrypted_device_token), + ) + .await?; - // Store device token saved on the server - // to prevent storing duplicate tokens. - // The server cannot deduplicate on its own - // because encryption gives a different - // result each time. - context - .set_config_internal(Config::DeviceToken, Some(&device_token)) - .await?; + if encrypted_device_token_len <= 4096 { + new_encrypted_device_token = Some(encrypted_device_token); } else { // If Apple or Google (FCM) gives us a very large token, // do not even try to give it to IMAP servers. @@ -1633,9 +1634,29 @@ impl Session { // of any length, but there is no reason for tokens // to be that large even after OpenPGP encryption. warn!(context, "Device token is too long for LITERAL-, ignoring."); + new_encrypted_device_token = None; } + } else { + new_encrypted_device_token = old_encrypted_device_token; + } + + // Store new encrypted device token on the server + // even if it is the same as the old one. + if let Some(encrypted_device_token) = new_encrypted_device_token { + let folder = context + .get_config(Config::ConfiguredInboxFolder) + .await? + .context("INBOX is not configured")?; + + self.run_command_and_check_ok(&format_setmetadata( + &folder, + &encrypted_device_token, + )) + .await + .context("SETMETADATA command failed")?; + + context.push_subscribed.store(true, Ordering::Relaxed); } - context.push_subscribed.store(true, Ordering::Relaxed); } else if !context.push_subscriber.heartbeat_subscribed().await { let context = context.clone(); // Subscribe for heartbeat notifications.