diff --git a/src/qr.rs b/src/qr.rs index 6453188033..553a2a9243 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -766,19 +766,18 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> { authcode, .. } => { - token::delete(context, token::Namespace::InviteNumber, &invitenumber).await?; - token::delete(context, token::Namespace::Auth, &authcode).await?; + token::delete(context, "").await?; context .sync_qr_code_token_deletion(invitenumber, authcode) .await?; } Qr::WithdrawVerifyGroup { + grpid, invitenumber, authcode, .. } => { - token::delete(context, token::Namespace::InviteNumber, &invitenumber).await?; - token::delete(context, token::Namespace::Auth, &authcode).await?; + token::delete(context, &grpid).await?; context .sync_qr_code_token_deletion(invitenumber, authcode) .await?; diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index 01cb0edbec..307cbbf5d4 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -2,7 +2,7 @@ use super::*; use crate::chat::{ProtectionStatus, create_group_chat}; use crate::config::Config; use crate::securejoin::get_securejoin_qr; -use crate::test_utils::{TestContext, TestContextManager}; +use crate::test_utils::{TestContext, TestContextManager, sync}; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decode_http() -> Result<()> { @@ -509,6 +509,77 @@ async fn test_withdraw_verifygroup() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_withdraw_multidevice() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let alice2 = &tcm.alice().await; + + alice.set_config_bool(Config::SyncMsgs, true).await?; + alice2.set_config_bool(Config::SyncMsgs, true).await?; + + // Alice creates two QR codes on the first device: + // group QR code and contact QR code. + let chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group").await?; + let chat2_id = create_group_chat(alice, ProtectionStatus::Unprotected, "Group 2").await?; + let contact_qr = get_securejoin_qr(alice, None).await?; + let group_qr = get_securejoin_qr(alice, Some(chat_id)).await?; + let group2_qr = get_securejoin_qr(alice, Some(chat2_id)).await?; + + assert!(matches!( + check_qr(alice, &contact_qr).await?, + Qr::WithdrawVerifyContact { .. } + )); + assert!(matches!( + check_qr(alice, &group_qr).await?, + Qr::WithdrawVerifyGroup { .. } + )); + + // Sync group QR codes. + sync(alice, alice2).await; + assert!(matches!( + check_qr(alice2, &group_qr).await?, + Qr::WithdrawVerifyGroup { .. } + )); + assert!(matches!( + check_qr(alice2, &group2_qr).await?, + Qr::WithdrawVerifyGroup { .. } + )); + + // Alice creates a contact QR code on second device + // and withdraws it. + let contact_qr2 = get_securejoin_qr(alice2, None).await?; + set_config_from_qr(alice2, &contact_qr2).await?; + assert!(matches!( + check_qr(alice2, &contact_qr2).await?, + Qr::ReviveVerifyContact { .. } + )); + + // Alice also withdraws second group QR code on second device. + set_config_from_qr(alice2, &group2_qr).await?; + + // Sync messages are sent from Alice's second device to first device. + sync(alice2, alice).await; + + // Now first device has reset all contact QR codes + // and second group QR code, + // but first group QR code is still valid. + assert!(matches!( + check_qr(alice, &contact_qr2).await?, + Qr::ReviveVerifyContact { .. } + )); + assert!(matches!( + check_qr(alice, &group_qr).await?, + Qr::WithdrawVerifyGroup { .. } + )); + assert!(matches!( + check_qr(alice, &group2_qr).await?, + Qr::ReviveVerifyGroup { .. } + )); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decode_and_apply_dclogin() -> Result<()> { let ctx = TestContext::new().await; diff --git a/src/sync.rs b/src/sync.rs index b5a42130b8..6342e78cfa 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -296,8 +296,15 @@ impl Context { } async fn delete_qr_token(&self, token: &QrTokenData) -> Result<()> { - token::delete(self, Namespace::InviteNumber, &token.invitenumber).await?; - token::delete(self, Namespace::Auth, &token.auth).await?; + self.sql + .execute( + "DELETE FROM tokens + WHERE foreign_key IN + (SELECT foreign_key FROM tokens + WHERE token=? OR token=?)", + (&token.invitenumber, &token.auth), + ) + .await?; Ok(()) } @@ -568,8 +575,8 @@ mod tests { .await? .is_none() ); - assert!(token::exists(&t, Namespace::InviteNumber, "yip-in").await?); - assert!(token::exists(&t, Namespace::Auth, "yip-auth").await?); + assert!(!token::exists(&t, Namespace::InviteNumber, "yip-in").await?); + assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await?); assert!(!token::exists(&t, Namespace::Auth, "non-existent").await?); assert!(!token::exists(&t, Namespace::Auth, "directly deleted").await?); diff --git a/src/token.rs b/src/token.rs index a5bdfc0681..7846e1e59a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -104,13 +104,14 @@ pub async fn auth_foreign_key(context: &Context, token: &str) -> Result Result<()> { +/// Resets all tokens corresponding to the `foreign_key`. +/// +/// `foreign_key` is a group ID to reset all group tokens +/// or empty string to reset all setup contact tokens. +pub async fn delete(context: &Context, foreign_key: &str) -> Result<()> { context .sql - .execute( - "DELETE FROM tokens WHERE namespc=? AND token=?;", - (namespace, token), - ) + .execute("DELETE FROM tokens WHERE foreign_key=?", (foreign_key,)) .await?; Ok(()) }