mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 17:36:29 +03:00
feat: Add broadcast QR type (todo: documentation)
This commit is contained in:
@@ -45,6 +45,7 @@ impl Lot {
|
|||||||
Self::Qr(qr) => match qr {
|
Self::Qr(qr) => match qr {
|
||||||
Qr::AskVerifyContact { .. } => None,
|
Qr::AskVerifyContact { .. } => None,
|
||||||
Qr::AskVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::AskVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||||
|
Qr::AskJoinBroadcast { broadcast_name, .. } => Some(Cow::Borrowed(broadcast_name)),
|
||||||
Qr::FprOk { .. } => None,
|
Qr::FprOk { .. } => None,
|
||||||
Qr::FprMismatch { .. } => None,
|
Qr::FprMismatch { .. } => None,
|
||||||
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
||||||
@@ -99,6 +100,7 @@ impl Lot {
|
|||||||
Self::Qr(qr) => match qr {
|
Self::Qr(qr) => match qr {
|
||||||
Qr::AskVerifyContact { .. } => LotState::QrAskVerifyContact,
|
Qr::AskVerifyContact { .. } => LotState::QrAskVerifyContact,
|
||||||
Qr::AskVerifyGroup { .. } => LotState::QrAskVerifyGroup,
|
Qr::AskVerifyGroup { .. } => LotState::QrAskVerifyGroup,
|
||||||
|
Qr::AskJoinBroadcast { .. } => LotState::QrAskJoinBroadcast,
|
||||||
Qr::FprOk { .. } => LotState::QrFprOk,
|
Qr::FprOk { .. } => LotState::QrFprOk,
|
||||||
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
||||||
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
||||||
@@ -126,6 +128,7 @@ impl Lot {
|
|||||||
Self::Qr(qr) => match qr {
|
Self::Qr(qr) => match qr {
|
||||||
Qr::AskVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
Qr::AskVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||||
Qr::AskVerifyGroup { .. } => Default::default(),
|
Qr::AskVerifyGroup { .. } => Default::default(),
|
||||||
|
Qr::AskJoinBroadcast { .. } => Default::default(),
|
||||||
Qr::FprOk { contact_id } => contact_id.to_u32(),
|
Qr::FprOk { contact_id } => contact_id.to_u32(),
|
||||||
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
||||||
Qr::FprWithoutAddr { .. } => Default::default(),
|
Qr::FprWithoutAddr { .. } => Default::default(),
|
||||||
@@ -169,6 +172,9 @@ pub enum LotState {
|
|||||||
/// text1=groupname
|
/// text1=groupname
|
||||||
QrAskVerifyGroup = 202,
|
QrAskVerifyGroup = 202,
|
||||||
|
|
||||||
|
/// text1=broadcast_name
|
||||||
|
QrAskJoinBroadcast = 204,
|
||||||
|
|
||||||
/// id=contact
|
/// id=contact
|
||||||
QrFprOk = 210,
|
QrFprOk = 210,
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,21 @@ pub enum QrObject {
|
|||||||
/// Authentication code.
|
/// Authentication code.
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
/// Ask the user whether to join the broadcast channel.
|
||||||
|
AskJoinBroadcast {
|
||||||
|
/// Chat name.
|
||||||
|
broadcast_name: String,
|
||||||
|
/// Group ID.
|
||||||
|
grpid: String,
|
||||||
|
/// ID of the contact.
|
||||||
|
contact_id: u32,
|
||||||
|
/// Fingerprint of the contact key as scanned from the QR code.
|
||||||
|
fingerprint: String,
|
||||||
|
|
||||||
|
/// The secret shared between all members,
|
||||||
|
/// used to symmetrically encrypt&decrypt messages.
|
||||||
|
shared_secret: String,
|
||||||
|
},
|
||||||
/// Contact fingerprint is verified.
|
/// Contact fingerprint is verified.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to start chatting.
|
/// Ask the user if they want to start chatting.
|
||||||
@@ -207,6 +222,23 @@ impl From<Qr> for QrObject {
|
|||||||
authcode,
|
authcode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Qr::AskJoinBroadcast {
|
||||||
|
broadcast_name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
shared_secret,
|
||||||
|
} => {
|
||||||
|
let contact_id = contact_id.to_u32();
|
||||||
|
let fingerprint = fingerprint.to_string();
|
||||||
|
QrObject::AskJoinBroadcast {
|
||||||
|
broadcast_name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
shared_secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
Qr::FprOk { contact_id } => {
|
Qr::FprOk { contact_id } => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
QrObject::FprOk { contact_id }
|
QrObject::FprOk { contact_id }
|
||||||
|
|||||||
45
src/qr.rs
45
src/qr.rs
@@ -84,6 +84,21 @@ pub enum Qr {
|
|||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Ask whether to join the broadcast channel.
|
||||||
|
AskJoinBroadcast {
|
||||||
|
// TODO document
|
||||||
|
broadcast_name: String,
|
||||||
|
|
||||||
|
// TODO not sure wheter it makes sense to call this grpid just because it's called like this in the db
|
||||||
|
grpid: String,
|
||||||
|
|
||||||
|
contact_id: ContactId,
|
||||||
|
|
||||||
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
shared_secret: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Contact fingerprint is verified.
|
/// Contact fingerprint is verified.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to start chatting.
|
/// Ask the user if they want to start chatting.
|
||||||
@@ -381,7 +396,7 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
|
|||||||
|
|
||||||
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
|
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&c=CHANNELNAME&x=CHANNELID&s=SHAREDSECRET`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=BROADCAST_NAME&x=BROADCAST_ID&b=BROADCAST_SHARED_SECRET`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
||||||
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||||
let payload = qr
|
let payload = qr
|
||||||
@@ -440,6 +455,10 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
.get("x")
|
.get("x")
|
||||||
.filter(|&s| validate_id(s))
|
.filter(|&s| validate_id(s))
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
|
let broadcast_shared_secret = param
|
||||||
|
.get("b")
|
||||||
|
.filter(|&s| validate_id(s))
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
let grpname = if grpid.is_some() {
|
let grpname = if grpid.is_some() {
|
||||||
if let Some(encoded_name) = param.get("g") {
|
if let Some(encoded_name) = param.get("g") {
|
||||||
@@ -526,6 +545,30 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
authcode,
|
authcode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if let (Some(addr), Some(broadcast_name), Some(grpid), Some(shared_secret)) =
|
||||||
|
(&addr, grpname, grpid, broadcast_shared_secret)
|
||||||
|
{
|
||||||
|
// This is a broadcast channel invite link.
|
||||||
|
// TODO code duplication with the previous block
|
||||||
|
// TODO at some point, we can mark this person as verified
|
||||||
|
let addr = ContactAddress::new(addr)?;
|
||||||
|
let (contact_id, _) = Contact::add_or_lookup_ex(
|
||||||
|
context,
|
||||||
|
&name,
|
||||||
|
&addr,
|
||||||
|
&fingerprint.hex(),
|
||||||
|
Origin::UnhandledSecurejoinQrScan,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to add or lookup contact for address {addr:?}"))?;
|
||||||
|
|
||||||
|
Ok(Qr::AskJoinBroadcast {
|
||||||
|
broadcast_name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
shared_secret,
|
||||||
|
})
|
||||||
} else if let Some(addr) = addr {
|
} else if let Some(addr) = addr {
|
||||||
let fingerprint = fingerprint.hex();
|
let fingerprint = fingerprint.hex();
|
||||||
let (contact_id, _) =
|
let (contact_id, _) =
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
|||||||
let hidden = match invite {
|
let hidden = match invite {
|
||||||
QrInvite::Contact { .. } => Blocked::Not,
|
QrInvite::Contact { .. } => Blocked::Not,
|
||||||
QrInvite::Group { .. } => Blocked::Yes,
|
QrInvite::Group { .. } => Blocked::Yes,
|
||||||
|
QrInvite::Broadcast { .. } => Blocked::Yes,
|
||||||
};
|
};
|
||||||
let chat_id = ChatId::create_for_contact_with_blocked(context, invite.contact_id(), hidden)
|
let chat_id = ChatId::create_for_contact_with_blocked(context, invite.contact_id(), hidden)
|
||||||
.await
|
.await
|
||||||
@@ -113,6 +114,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
|||||||
chat::add_info_msg(context, group_chat_id, &msg, time()).await?;
|
chat::add_info_msg(context, group_chat_id, &msg, time()).await?;
|
||||||
Ok(group_chat_id)
|
Ok(group_chat_id)
|
||||||
}
|
}
|
||||||
|
QrInvite::Broadcast { .. } => {}
|
||||||
QrInvite::Contact { .. } => {
|
QrInvite::Contact { .. } => {
|
||||||
// For setup-contact the BobState already ensured the 1:1 chat exists because it
|
// For setup-contact the BobState already ensured the 1:1 chat exists because it
|
||||||
// uses it to send the handshake messages.
|
// uses it to send the handshake messages.
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ pub enum QrInvite {
|
|||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
|
Broadcast {
|
||||||
|
broadcast_name: String,
|
||||||
|
grpid: String,
|
||||||
|
contact_id: ContactId,
|
||||||
|
fingerprint: Fingerprint,
|
||||||
|
shared_secret: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QrInvite {
|
impl QrInvite {
|
||||||
@@ -95,6 +102,19 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
}),
|
}),
|
||||||
|
Qr::AskJoinBroadcast {
|
||||||
|
broadcast_name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
shared_secret,
|
||||||
|
} => Ok(QrInvite::Broadcast {
|
||||||
|
broadcast_name,
|
||||||
|
grpid,
|
||||||
|
contact_id,
|
||||||
|
fingerprint,
|
||||||
|
shared_secret,
|
||||||
|
}),
|
||||||
_ => bail!("Unsupported QR type"),
|
_ => bail!("Unsupported QR type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user