mirror of
https://github.com/chatmail/core.git
synced 2026-05-22 16:26:31 +03:00
feat: no unencrypted chat when securejoin times out (#6722)
this PR leaves one-to-one chats that were created by a QR code scan unwritable until e2ee is established. the logic of the timeout is reused to show a message with additional information: <img width=250 src=https://github.com/user-attachments/assets/b9928e7b-8128-4d7a-934d-37d51c8275ce> <img width=250 src=https://github.com/user-attachments/assets/4a3a28e9-4491-47f9-8962-86aa2302dd21> <img width=250 src=https://github.com/user-attachments/assets/5130a87c-ba1c-496f-81e1-899dc8aabe4e> if the secure-join finishes faster than the 15 seconds, the middle message is not shown. closes #6706
This commit is contained in:
@@ -7569,9 +7569,14 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
|
|
||||||
/// "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
|
/// "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
|
||||||
///
|
///
|
||||||
/// Used as info message.
|
/// @deprecated 2025-03
|
||||||
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
|
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
|
||||||
|
|
||||||
|
/// "This takes longer than expected, maybe devices are offline…\n\nHowever, the process continues in background, you can do something else"
|
||||||
|
///
|
||||||
|
/// Used as info message.
|
||||||
|
#define DC_STR_SECUREJOIN_TAKES_LONGER 192
|
||||||
|
|
||||||
/// "Contact". Deprecated, currently unused.
|
/// "Contact". Deprecated, currently unused.
|
||||||
#define DC_STR_CONTACT 200
|
#define DC_STR_CONTACT 200
|
||||||
|
|
||||||
|
|||||||
58
src/chat.rs
58
src/chat.rs
@@ -130,8 +130,7 @@ pub(crate) enum CantSendReason {
|
|||||||
/// Not a member of the chat.
|
/// Not a member of the chat.
|
||||||
NotAMember,
|
NotAMember,
|
||||||
|
|
||||||
/// Temporary state for 1:1 chats while SecureJoin is in progress, after a timeout sending
|
/// Temporary state for 1:1 chats while SecureJoin is in progress.
|
||||||
/// messages (incl. unencrypted if we don't yet know the contact's pubkey) is allowed.
|
|
||||||
SecurejoinWait,
|
SecurejoinWait,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1727,13 +1726,13 @@ impl Chat {
|
|||||||
return Ok(Some(reason));
|
return Ok(Some(reason));
|
||||||
}
|
}
|
||||||
let reason = SecurejoinWait;
|
let reason = SecurejoinWait;
|
||||||
if !skip_fn(&reason)
|
if !skip_fn(&reason) {
|
||||||
&& self
|
let (can_write, _) = self
|
||||||
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
|
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
|
||||||
.await?
|
.await?;
|
||||||
> 0
|
if !can_write {
|
||||||
{
|
return Ok(Some(reason));
|
||||||
return Ok(Some(reason));
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -1745,28 +1744,32 @@ impl Chat {
|
|||||||
Ok(self.why_cant_send(context).await?.is_none())
|
Ok(self.why_cant_send(context).await?.is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the remaining timeout for the 1:1 chat in-progress SecureJoin.
|
/// Returns if the chat can be sent to
|
||||||
|
/// and the remaining timeout for the 1:1 chat in-progress SecureJoin.
|
||||||
///
|
///
|
||||||
/// If the timeout has expired, notifies the user that sending messages is possible. See also
|
/// If the timeout has expired, adds an info message with additional information;
|
||||||
/// [`CantSendReason::SecurejoinWait`].
|
/// the chat still cannot be sent to in this case. See also [`CantSendReason::SecurejoinWait`].
|
||||||
pub(crate) async fn check_securejoin_wait(
|
pub(crate) async fn check_securejoin_wait(
|
||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
) -> Result<u64> {
|
) -> Result<(bool, u64)> {
|
||||||
if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
|
if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
|
||||||
return Ok(0);
|
return Ok((true, 0));
|
||||||
}
|
}
|
||||||
let (mut param0, mut param1) = (Params::new(), Params::new());
|
|
||||||
param0.set_cmd(SystemMessage::SecurejoinWait);
|
// chat is single and unprotected:
|
||||||
param1.set_cmd(SystemMessage::SecurejoinWaitTimeout);
|
// get last info message of type SecurejoinWait or SecurejoinWaitTimeout
|
||||||
let (param0, param1) = (param0.to_string(), param1.to_string());
|
let (mut param_wait, mut param_timeout) = (Params::new(), Params::new());
|
||||||
|
param_wait.set_cmd(SystemMessage::SecurejoinWait);
|
||||||
|
param_timeout.set_cmd(SystemMessage::SecurejoinWaitTimeout);
|
||||||
|
let (param_wait, param_timeout) = (param_wait.to_string(), param_timeout.to_string());
|
||||||
let Some((param, ts_sort, ts_start)) = context
|
let Some((param, ts_sort, ts_start)) = context
|
||||||
.sql
|
.sql
|
||||||
.query_row_optional(
|
.query_row_optional(
|
||||||
"SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
|
"SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
|
||||||
(SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
|
(SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
|
||||||
(self.id, ¶m0, ¶m1),
|
(self.id, ¶m_wait, ¶m_timeout),
|
||||||
|row| {
|
|row| {
|
||||||
let param: String = row.get(0)?;
|
let param: String = row.get(0)?;
|
||||||
let ts_sort: i64 = row.get(1)?;
|
let ts_sort: i64 = row.get(1)?;
|
||||||
@@ -1776,11 +1779,13 @@ impl Chat {
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
else {
|
else {
|
||||||
return Ok(0);
|
return Ok((true, 0));
|
||||||
};
|
};
|
||||||
if param == param1 {
|
|
||||||
return Ok(0);
|
if param == param_timeout {
|
||||||
|
return Ok((false, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = time();
|
let now = time();
|
||||||
// Don't await SecureJoin if the clock was set back.
|
// Don't await SecureJoin if the clock was set back.
|
||||||
if ts_start <= now {
|
if ts_start <= now {
|
||||||
@@ -1788,13 +1793,14 @@ impl Chat {
|
|||||||
.saturating_add(timeout.try_into()?)
|
.saturating_add(timeout.try_into()?)
|
||||||
.saturating_sub(now);
|
.saturating_sub(now);
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
return Ok(timeout as u64);
|
return Ok((false, timeout as u64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_info_msg_with_cmd(
|
add_info_msg_with_cmd(
|
||||||
context,
|
context,
|
||||||
self.id,
|
self.id,
|
||||||
&stock_str::securejoin_wait_timeout(context).await,
|
&stock_str::securejoin_takes_longer(context).await,
|
||||||
SystemMessage::SecurejoinWaitTimeout,
|
SystemMessage::SecurejoinWaitTimeout,
|
||||||
// Use the sort timestamp of the "please wait" message, this way the added message is
|
// Use the sort timestamp of the "please wait" message, this way the added message is
|
||||||
// never sorted below the protection message if the SecureJoin finishes in parallel.
|
// never sorted below the protection message if the SecureJoin finishes in parallel.
|
||||||
@@ -1805,8 +1811,8 @@ impl Chat {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
context.emit_event(EventType::ChatModified(self.id));
|
|
||||||
Ok(0)
|
Ok((false, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the user is part of a chat
|
/// Checks if the user is part of a chat
|
||||||
@@ -2611,7 +2617,7 @@ pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
|
|||||||
|
|
||||||
for chat_id in chat_ids {
|
for chat_id in chat_ids {
|
||||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||||
let timeout = chat
|
let (_, timeout) = chat
|
||||||
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
|
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
|
||||||
.await?;
|
.await?;
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
|||||||
);
|
);
|
||||||
if case == SetupContactCase::SecurejoinWaitTimeout {
|
if case == SetupContactCase::SecurejoinWaitTimeout {
|
||||||
SystemTime::shift(Duration::from_secs(constants::SECUREJOIN_WAIT_TIMEOUT));
|
SystemTime::shift(Duration::from_secs(constants::SECUREJOIN_WAIT_TIMEOUT));
|
||||||
assert_eq!(bob_chat.can_send(&bob).await.unwrap(), true);
|
assert_eq!(bob_chat.can_send(&bob).await.unwrap(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Bob receives vc-auth-required, sends vc-request-with-auth
|
// Step 4: Bob receives vc-auth-required, sends vc-request-with-auth
|
||||||
@@ -318,7 +318,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
|||||||
.check_securejoin_wait(&bob, constants::SECUREJOIN_WAIT_TIMEOUT)
|
.check_securejoin_wait(&bob, constants::SECUREJOIN_WAIT_TIMEOUT)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
0
|
(true, 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
|||||||
assert!(msg.is_info());
|
assert!(msg.is_info());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
msg.get_text(),
|
msg.get_text(),
|
||||||
stock_str::securejoin_wait_timeout(&bob).await
|
stock_str::securejoin_takes_longer(&bob).await
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
|
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
|
||||||
|
|||||||
@@ -438,9 +438,9 @@ pub enum StockMessage {
|
|||||||
SecurejoinWait = 190,
|
SecurejoinWait = 190,
|
||||||
|
|
||||||
#[strum(props(
|
#[strum(props(
|
||||||
fallback = "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
|
fallback = "This takes longer than expected, maybe devices are offline…\n\nHowever, the process continues in background, you can do something else 🕺"
|
||||||
))]
|
))]
|
||||||
SecurejoinWaitTimeout = 191,
|
SecurejoinTakesLonger = 192,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StockMessage {
|
impl StockMessage {
|
||||||
@@ -833,8 +833,8 @@ pub(crate) async fn securejoin_wait(context: &Context) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stock string: `Could not yet establish guaranteed end-to-end encryption, but you may already send a message.`.
|
/// Stock string: `Could not yet establish guaranteed end-to-end encryption, but you may already send a message.`.
|
||||||
pub(crate) async fn securejoin_wait_timeout(context: &Context) -> String {
|
pub(crate) async fn securejoin_takes_longer(context: &Context) -> String {
|
||||||
translated(context, StockMessage::SecurejoinWaitTimeout).await
|
translated(context, StockMessage::SecurejoinTakesLonger).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stock string: `Scan to chat with %1$s`.
|
/// Stock string: `Scan to chat with %1$s`.
|
||||||
|
|||||||
Reference in New Issue
Block a user