Skip to content

Commit 3133d89

Browse files
authored
fix: Let securejoin succeed even if the chat was deleted in the meantime (#7594)
Fix #7478 by creating the 1:1 chat in `handle_auth_required` if it doesn't exist anymore.
1 parent 9977545 commit 3133d89

File tree

3 files changed

+66
-18
lines changed

3 files changed

+66
-18
lines changed

src/securejoin/bob.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
4343
// A 1:1 chat is needed to send messages to Alice. When joining a group this chat is
4444
// hidden, if a user starts sending messages in it it will be unhidden in
4545
// receive_imf.
46-
let hidden = match invite {
47-
QrInvite::Contact { .. } => Blocked::Not,
48-
QrInvite::Group { .. } => Blocked::Yes,
49-
QrInvite::Broadcast { .. } => Blocked::Yes,
50-
};
51-
52-
// The 1:1 chat with the inviter
53-
let private_chat_id =
54-
ChatId::create_for_contact_with_blocked(context, invite.contact_id(), hidden)
55-
.await
56-
.with_context(|| format!("can't create chat for contact {}", invite.contact_id()))?;
46+
let private_chat_id = private_chat_id(context, &invite).await?;
5747

5848
ContactId::scaleup_origin(context, &[invite.contact_id()], Origin::SecurejoinJoined).await?;
5949
context.emit_event(EventType::ContactsChanged(None));
@@ -175,6 +165,9 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
175165
///
176166
/// Returns the ID of the newly inserted entry.
177167
async fn insert_new_db_entry(context: &Context, invite: QrInvite, chat_id: ChatId) -> Result<i64> {
168+
// The `chat_id` isn't actually needed anymore,
169+
// but we still save it;
170+
// can be removed as a future improvement.
178171
context
179172
.sql
180173
.insert(
@@ -195,11 +188,10 @@ pub(super) async fn handle_auth_required(
195188
// Load all Bob states that expect `vc-auth-required` or `vg-auth-required`.
196189
let bob_states = context
197190
.sql
198-
.query_map_vec("SELECT id, invite, chat_id FROM bobstate", (), |row| {
191+
.query_map_vec("SELECT id, invite FROM bobstate", (), |row| {
199192
let row_id: i64 = row.get(0)?;
200193
let invite: QrInvite = row.get(1)?;
201-
let chat_id: ChatId = row.get(2)?;
202-
Ok((row_id, invite, chat_id))
194+
Ok((row_id, invite))
203195
})
204196
.await?;
205197

@@ -209,7 +201,7 @@ pub(super) async fn handle_auth_required(
209201
);
210202

211203
let mut auth_sent = false;
212-
for (bobstate_row_id, invite, chat_id) in bob_states {
204+
for (bobstate_row_id, invite) in bob_states {
213205
if !encrypted_and_signed(context, message, invite.fingerprint()) {
214206
continue;
215207
}
@@ -220,6 +212,7 @@ pub(super) async fn handle_auth_required(
220212
}
221213

222214
info!(context, "Fingerprint verified.",);
215+
let chat_id = private_chat_id(context, &invite).await?;
223216
send_handshake_message(context, &invite, chat_id, BobHandshakeMsg::RequestWithAuth).await?;
224217
context
225218
.sql
@@ -348,6 +341,22 @@ impl BobHandshakeMsg {
348341
}
349342
}
350343

344+
/// Returns the 1:1 chat with the inviter.
345+
///
346+
/// This is the chat in which securejoin messages are sent.
347+
/// The 1:1 chat will be created if it does not yet exist.
348+
async fn private_chat_id(context: &Context, invite: &QrInvite) -> Result<ChatId> {
349+
let hidden = match invite {
350+
QrInvite::Contact { .. } => Blocked::Not,
351+
QrInvite::Group { .. } => Blocked::Yes,
352+
QrInvite::Broadcast { .. } => Blocked::Yes,
353+
};
354+
355+
ChatId::create_for_contact_with_blocked(context, invite.contact_id(), hidden)
356+
.await
357+
.with_context(|| format!("can't create chat for contact {}", invite.contact_id()))
358+
}
359+
351360
/// Returns the [`ChatId`] of the chat being joined.
352361
///
353362
/// This is the chat in which you want to notify the user as well.

src/securejoin/securejoin_tests.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
243243
.unwrap();
244244
match case {
245245
SetupContactCase::AliceHasName => assert_eq!(contact_alice.get_authname(), "Alice"),
246-
_ => assert_eq!(contact_alice.get_authname(), "Alice Exampleorg"),
246+
_ => assert_eq!(contact_alice.get_authname(), ""),
247247
};
248248

249249
// Check Alice sent the right message to Bob.
@@ -1217,3 +1217,33 @@ async fn test_qr_no_implicit_inviter_addition() -> Result<()> {
12171217

12181218
Ok(())
12191219
}
1220+
1221+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1222+
async fn test_user_deletes_chat_before_securejoin_completes() -> Result<()> {
1223+
let mut tcm = TestContextManager::new();
1224+
let alice = &tcm.alice().await;
1225+
let bob = &tcm.bob().await;
1226+
1227+
let qr = get_securejoin_qr(alice, None).await?;
1228+
let bob_chat_id = join_securejoin(bob, &qr).await?;
1229+
1230+
let bob_alice_chat = bob.get_chat(alice).await;
1231+
// It's not possible yet to send to the chat, because Bob doesn't have Alice's key:
1232+
assert_eq!(bob_alice_chat.can_send(bob).await?, false);
1233+
assert_eq!(bob_alice_chat.id, bob_chat_id);
1234+
1235+
let request = bob.pop_sent_msg().await;
1236+
1237+
bob_chat_id.delete(bob).await?;
1238+
1239+
alice.recv_msg_trash(&request).await;
1240+
let auth_required = alice.pop_sent_msg().await;
1241+
1242+
bob.recv_msg_trash(&auth_required).await;
1243+
1244+
// The chat with Alice should be recreated,
1245+
// and it should be sendable now:
1246+
assert!(bob.get_chat(alice).await.can_send(bob).await?);
1247+
1248+
Ok(())
1249+
}

src/test_utils.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,15 @@ impl TestContext {
896896
/// If the contact does not exist yet, a new contact will be created
897897
/// with the correct fingerprint, but without the public key.
898898
pub async fn add_or_lookup_contact_no_key(&self, other: &TestContext) -> Contact {
899+
let contact_id = self.add_or_lookup_contact_id_no_key(other).await;
900+
Contact::get_by_id(&self.ctx, contact_id).await.unwrap()
901+
}
902+
903+
/// Returns the [`ContactId`] for the other [`TestContext`], creating it if necessary.
904+
///
905+
/// If the contact does not exist yet, a new contact will be created
906+
/// with the correct fingerprint, but without the public key.
907+
async fn add_or_lookup_contact_id_no_key(&self, other: &TestContext) -> ContactId {
899908
let primary_self_addr = other.ctx.get_primary_self_addr().await.unwrap();
900909
let addr = ContactAddress::new(&primary_self_addr).unwrap();
901910
let fingerprint = self_fingerprint(other).await.unwrap();
@@ -904,7 +913,7 @@ impl TestContext {
904913
Contact::add_or_lookup_ex(self, "", &addr, fingerprint, Origin::MailinglistAddress)
905914
.await
906915
.expect("add_or_lookup");
907-
Contact::get_by_id(&self.ctx, contact_id).await.unwrap()
916+
contact_id
908917
}
909918

910919
/// Returns 1:1 [`Chat`] with another account address-contact.
@@ -935,7 +944,7 @@ impl TestContext {
935944
/// so may create a key-contact with a fingerprint
936945
/// but without the key.
937946
pub async fn get_chat(&self, other: &TestContext) -> Chat {
938-
let contact = self.add_or_lookup_contact_id(other).await;
947+
let contact = self.add_or_lookup_contact_id_no_key(other).await;
939948

940949
let chat_id = ChatIdBlocked::lookup_by_contact(&self.ctx, contact)
941950
.await

0 commit comments

Comments
 (0)