mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
feat!: Withdraw broadcast invites. Add Qr::WithdrawJoinBroadcast and Qr::ReviveJoinBroadcast QR code types. (#7439)
Add the ability to withdraw broadcast invite codes After merging: - [x] Create issues in iOS, Desktop and UT repositories
This commit is contained in:
@@ -2578,8 +2578,10 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
|||||||
#define DC_QR_ERROR 400 // text1=error string
|
#define DC_QR_ERROR 400 // text1=error string
|
||||||
#define DC_QR_WITHDRAW_VERIFYCONTACT 500
|
#define DC_QR_WITHDRAW_VERIFYCONTACT 500
|
||||||
#define DC_QR_WITHDRAW_VERIFYGROUP 502 // text1=groupname
|
#define DC_QR_WITHDRAW_VERIFYGROUP 502 // text1=groupname
|
||||||
|
#define DC_QR_WITHDRAW_JOINBROADCAST 504 // text1=broadcast name
|
||||||
#define DC_QR_REVIVE_VERIFYCONTACT 510
|
#define DC_QR_REVIVE_VERIFYCONTACT 510
|
||||||
#define DC_QR_REVIVE_VERIFYGROUP 512 // text1=groupname
|
#define DC_QR_REVIVE_VERIFYGROUP 512 // text1=groupname
|
||||||
|
#define DC_QR_REVIVE_JOINBROADCAST 514 // text1=broadcast name
|
||||||
#define DC_QR_LOGIN 520 // text1=email_address
|
#define DC_QR_LOGIN 520 // text1=email_address
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,8 +58,10 @@ impl Lot {
|
|||||||
Qr::Text { text } => Some(Cow::Borrowed(text)),
|
Qr::Text { text } => Some(Cow::Borrowed(text)),
|
||||||
Qr::WithdrawVerifyContact { .. } => None,
|
Qr::WithdrawVerifyContact { .. } => None,
|
||||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||||
|
Qr::WithdrawJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||||
Qr::ReviveVerifyContact { .. } => None,
|
Qr::ReviveVerifyContact { .. } => None,
|
||||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||||
|
Qr::ReviveJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||||
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
|
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
|
||||||
},
|
},
|
||||||
Self::Error(err) => Some(Cow::Borrowed(err)),
|
Self::Error(err) => Some(Cow::Borrowed(err)),
|
||||||
@@ -112,8 +114,10 @@ impl Lot {
|
|||||||
Qr::Text { .. } => LotState::QrText,
|
Qr::Text { .. } => LotState::QrText,
|
||||||
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
|
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
|
||||||
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
||||||
|
Qr::WithdrawJoinBroadcast { .. } => LotState::QrWithdrawJoinBroadcast,
|
||||||
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
||||||
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
||||||
|
Qr::ReviveJoinBroadcast { .. } => LotState::QrReviveJoinBroadcast,
|
||||||
Qr::Login { .. } => LotState::QrLogin,
|
Qr::Login { .. } => LotState::QrLogin,
|
||||||
},
|
},
|
||||||
Self::Error(_err) => LotState::QrError,
|
Self::Error(_err) => LotState::QrError,
|
||||||
@@ -138,9 +142,11 @@ impl Lot {
|
|||||||
Qr::Url { .. } => Default::default(),
|
Qr::Url { .. } => Default::default(),
|
||||||
Qr::Text { .. } => Default::default(),
|
Qr::Text { .. } => Default::default(),
|
||||||
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||||
Qr::WithdrawVerifyGroup { .. } => Default::default(),
|
Qr::WithdrawVerifyGroup { .. } | Qr::WithdrawJoinBroadcast { .. } => {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||||
Qr::ReviveVerifyGroup { .. } => Default::default(),
|
Qr::ReviveVerifyGroup { .. } | Qr::ReviveJoinBroadcast { .. } => Default::default(),
|
||||||
Qr::Login { .. } => Default::default(),
|
Qr::Login { .. } => Default::default(),
|
||||||
},
|
},
|
||||||
Self::Error(_) => Default::default(),
|
Self::Error(_) => Default::default(),
|
||||||
@@ -207,11 +213,15 @@ pub enum LotState {
|
|||||||
|
|
||||||
/// text1=groupname
|
/// text1=groupname
|
||||||
QrWithdrawVerifyGroup = 502,
|
QrWithdrawVerifyGroup = 502,
|
||||||
|
/// text1=broadcast channel name
|
||||||
|
QrWithdrawJoinBroadcast = 504,
|
||||||
|
|
||||||
QrReviveVerifyContact = 510,
|
QrReviveVerifyContact = 510,
|
||||||
|
|
||||||
/// text1=groupname
|
/// text1=groupname
|
||||||
QrReviveVerifyGroup = 512,
|
QrReviveVerifyGroup = 512,
|
||||||
|
/// text1=groupname
|
||||||
|
QrReviveJoinBroadcast = 514,
|
||||||
|
|
||||||
/// text1=email_address
|
/// text1=email_address
|
||||||
QrLogin = 520,
|
QrLogin = 520,
|
||||||
|
|||||||
@@ -157,6 +157,21 @@ pub enum QrObject {
|
|||||||
/// Authentication code.
|
/// Authentication code.
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
|
||||||
|
WithdrawJoinBroadcast {
|
||||||
|
/// Broadcast name.
|
||||||
|
name: String,
|
||||||
|
/// ID, uniquely identifying this chat. Called grpid for historic reasons.
|
||||||
|
grpid: String,
|
||||||
|
/// Contact ID. Always `ContactId::SELF`.
|
||||||
|
contact_id: u32,
|
||||||
|
/// Fingerprint of the contact key as scanned from the QR code.
|
||||||
|
fingerprint: String,
|
||||||
|
/// Invite number.
|
||||||
|
invitenumber: String,
|
||||||
|
/// Authentication code.
|
||||||
|
authcode: String,
|
||||||
|
},
|
||||||
/// Ask the user if they want to revive their own QR code.
|
/// Ask the user if they want to revive their own QR code.
|
||||||
ReviveVerifyContact {
|
ReviveVerifyContact {
|
||||||
/// Contact ID.
|
/// Contact ID.
|
||||||
@@ -183,6 +198,21 @@ pub enum QrObject {
|
|||||||
/// Authentication code.
|
/// Authentication code.
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
/// Ask the user if they want to revive their own broadcast channel invite QR code.
|
||||||
|
ReviveJoinBroadcast {
|
||||||
|
/// Broadcast name.
|
||||||
|
name: String,
|
||||||
|
/// Globally unique chat ID. Called grpid for historic reasons.
|
||||||
|
grpid: String,
|
||||||
|
/// Contact ID. Always `ContactId::SELF`.
|
||||||
|
contact_id: u32,
|
||||||
|
/// Fingerprint of the contact key as scanned from the QR code.
|
||||||
|
fingerprint: String,
|
||||||
|
/// Invite number.
|
||||||
|
invitenumber: String,
|
||||||
|
/// Authentication code.
|
||||||
|
authcode: String,
|
||||||
|
},
|
||||||
/// `dclogin:` scheme parameters.
|
/// `dclogin:` scheme parameters.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to login with the email address.
|
/// Ask the user if they want to login with the email address.
|
||||||
@@ -306,6 +336,25 @@ impl From<Qr> for QrObject {
|
|||||||
authcode,
|
authcode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Qr::WithdrawJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
} => {
|
||||||
|
let contact_id = contact_id.to_u32();
|
||||||
|
let fingerprint = fingerprint.to_string();
|
||||||
|
QrObject::WithdrawJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
}
|
||||||
|
}
|
||||||
Qr::ReviveVerifyContact {
|
Qr::ReviveVerifyContact {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
@@ -340,6 +389,25 @@ impl From<Qr> for QrObject {
|
|||||||
authcode,
|
authcode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Qr::ReviveJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
} => {
|
||||||
|
let contact_id = contact_id.to_u32();
|
||||||
|
let fingerprint = fingerprint.to_string();
|
||||||
|
QrObject::ReviveJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
}
|
||||||
|
}
|
||||||
Qr::Login { address, .. } => QrObject::Login { address },
|
Qr::Login { address, .. } => QrObject::Login { address },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
src/qr.rs
104
src/qr.rs
@@ -233,6 +233,31 @@ pub enum Qr {
|
|||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Ask the user if they want to withdraw their own broadcast channel invite QR code.
|
||||||
|
WithdrawJoinBroadcast {
|
||||||
|
/// The user-visible name of this broadcast channel
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// A string of random characters,
|
||||||
|
/// uniquely identifying this broadcast channel across all databases/clients.
|
||||||
|
/// Called `grpid` for historic reasons:
|
||||||
|
/// The id of multi-user chats is always called `grpid` in the database
|
||||||
|
/// because groups were once the only multi-user chats.
|
||||||
|
grpid: String,
|
||||||
|
|
||||||
|
/// Contact ID. Always `ContactId::SELF`.
|
||||||
|
contact_id: ContactId,
|
||||||
|
|
||||||
|
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||||
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
/// Invite number.
|
||||||
|
invitenumber: String,
|
||||||
|
|
||||||
|
/// Authentication code.
|
||||||
|
authcode: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Ask the user if they want to revive their own QR code.
|
/// Ask the user if they want to revive their own QR code.
|
||||||
ReviveVerifyContact {
|
ReviveVerifyContact {
|
||||||
/// Contact ID.
|
/// Contact ID.
|
||||||
@@ -269,6 +294,31 @@ pub enum Qr {
|
|||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Ask the user if they want to revive their own broadcast channel invite QR code.
|
||||||
|
ReviveJoinBroadcast {
|
||||||
|
/// The user-visible name of this broadcast channel
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// A string of random characters,
|
||||||
|
/// uniquely identifying this broadcast channel across all databases/clients.
|
||||||
|
/// Called `grpid` for historic reasons:
|
||||||
|
/// The id of multi-user chats is always called `grpid` in the database
|
||||||
|
/// because groups were once the only multi-user chats.
|
||||||
|
grpid: String,
|
||||||
|
|
||||||
|
/// Contact ID. Always `ContactId::SELF`.
|
||||||
|
contact_id: ContactId,
|
||||||
|
|
||||||
|
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||||
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
/// Invite number.
|
||||||
|
invitenumber: String,
|
||||||
|
|
||||||
|
/// Authentication code.
|
||||||
|
authcode: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// `dclogin:` scheme parameters.
|
/// `dclogin:` scheme parameters.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to login with the email address.
|
/// Ask the user if they want to login with the email address.
|
||||||
@@ -500,14 +550,40 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if let (Some(grpid), Some(name)) = (grpid, broadcast_name) {
|
} else if let (Some(grpid), Some(name)) = (grpid, broadcast_name) {
|
||||||
Ok(Qr::AskJoinBroadcast {
|
if context
|
||||||
name,
|
.is_self_addr(&addr)
|
||||||
grpid,
|
.await
|
||||||
contact_id,
|
.with_context(|| format!("Can't check if {addr:?} is our address"))?
|
||||||
fingerprint,
|
{
|
||||||
invitenumber,
|
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||||
authcode,
|
Ok(Qr::WithdrawJoinBroadcast {
|
||||||
})
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Qr::ReviveJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Qr::AskJoinBroadcast {
|
||||||
|
name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if context.is_self_addr(&addr).await? {
|
} else if context.is_self_addr(&addr).await? {
|
||||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||||
Ok(Qr::WithdrawVerifyContact {
|
Ok(Qr::WithdrawVerifyContact {
|
||||||
@@ -800,6 +876,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
|||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
..
|
..
|
||||||
|
}
|
||||||
|
| Qr::WithdrawJoinBroadcast {
|
||||||
|
grpid,
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
token::delete(context, &grpid).await?;
|
token::delete(context, &grpid).await?;
|
||||||
context
|
context
|
||||||
@@ -829,6 +911,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
|||||||
authcode,
|
authcode,
|
||||||
grpid,
|
grpid,
|
||||||
..
|
..
|
||||||
|
}
|
||||||
|
| Qr::ReviveJoinBroadcast {
|
||||||
|
invitenumber,
|
||||||
|
authcode,
|
||||||
|
grpid,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let timestamp = time();
|
let timestamp = time();
|
||||||
token::save(
|
token::save(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::chat::create_group;
|
use crate::chat::{Chat, create_broadcast, create_group, get_chat_contacts};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::login_param::EnteredCertificateChecks;
|
use crate::login_param::EnteredCertificateChecks;
|
||||||
use crate::provider::Socket;
|
use crate::provider::Socket;
|
||||||
@@ -511,6 +511,56 @@ async fn test_withdraw_verifygroup() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_withdraw_joinbroadcast() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
let chat_id = create_broadcast(alice, "foo".to_string()).await?;
|
||||||
|
let qr = get_securejoin_qr(alice, Some(chat_id)).await?;
|
||||||
|
|
||||||
|
// scanning own verify-group code offers withdrawing
|
||||||
|
if let Qr::WithdrawJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
|
||||||
|
assert_eq!(name, "foo");
|
||||||
|
} else {
|
||||||
|
bail!("Wrong QR type, expected WithdrawJoinBroadcast");
|
||||||
|
}
|
||||||
|
set_config_from_qr(alice, &qr).await?;
|
||||||
|
|
||||||
|
// scanning withdrawn verify-group code offers reviving
|
||||||
|
if let Qr::ReviveJoinBroadcast { name, .. } = check_qr(alice, &qr).await? {
|
||||||
|
assert_eq!(name, "foo");
|
||||||
|
} else {
|
||||||
|
bail!("Wrong QR type, expected ReviveJoinBroadcast");
|
||||||
|
}
|
||||||
|
|
||||||
|
// someone else always scans as ask-verify-group
|
||||||
|
if let Qr::AskJoinBroadcast { name, .. } = check_qr(bob, &qr).await? {
|
||||||
|
assert_eq!(name, "foo");
|
||||||
|
} else {
|
||||||
|
bail!("Wrong QR type, expected AskJoinBroadcast");
|
||||||
|
}
|
||||||
|
assert!(set_config_from_qr(bob, &qr).await.is_err());
|
||||||
|
|
||||||
|
// Bob can't join using this QR code, since it's still withdrawn
|
||||||
|
let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||||
|
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
|
||||||
|
assert_eq!(bob_chat.is_self_in_chat(bob).await?, false);
|
||||||
|
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 0);
|
||||||
|
|
||||||
|
// Revive
|
||||||
|
set_config_from_qr(alice, &qr).await?;
|
||||||
|
|
||||||
|
// Now Bob can join
|
||||||
|
let bob_chat_id2 = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||||
|
assert_eq!(bob_chat_id, bob_chat_id2);
|
||||||
|
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
|
||||||
|
assert_eq!(bob_chat.is_self_in_chat(bob).await?, true);
|
||||||
|
assert_eq!(get_chat_contacts(alice, chat_id).await?.len(), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_withdraw_multidevice() -> Result<()> {
|
async fn test_withdraw_multidevice() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user