deltachat/securejoin/
qrinvite.rs

1//! Supporting code for the QR-code invite.
2//!
3//! QR-codes are decoded into a more general-purpose [`Qr`] struct normally.  This makes working
4//! with it rather hard, so here we have a wrapper type that specifically deals with Secure-Join
5//! QR-codes so that the Secure-Join code can have more guarantees when dealing with this.
6
7use anyhow::{Error, Result, bail};
8
9use crate::contact::ContactId;
10use crate::key::Fingerprint;
11use crate::qr::Qr;
12
13/// Represents the data from a QR-code scan.
14///
15/// There are methods to conveniently access fields present in all three variants.
16#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17pub enum QrInvite {
18    Contact {
19        contact_id: ContactId,
20        fingerprint: Fingerprint,
21        invitenumber: String,
22        authcode: String,
23        #[serde(default)]
24        is_v3: bool,
25    },
26    Group {
27        contact_id: ContactId,
28        fingerprint: Fingerprint,
29        name: String,
30        grpid: String,
31        invitenumber: String,
32        authcode: String,
33        #[serde(default)]
34        is_v3: bool,
35    },
36    Broadcast {
37        contact_id: ContactId,
38        fingerprint: Fingerprint,
39        name: String,
40        grpid: String,
41        invitenumber: String,
42        authcode: String,
43        #[serde(default)]
44        is_v3: bool,
45    },
46}
47
48impl QrInvite {
49    /// The contact ID of the inviter.
50    ///
51    /// The actual QR-code contains a URL-encoded email address, but upon scanning this is
52    /// translated to a contact ID.
53    pub fn contact_id(&self) -> ContactId {
54        match self {
55            Self::Contact { contact_id, .. }
56            | Self::Group { contact_id, .. }
57            | Self::Broadcast { contact_id, .. } => *contact_id,
58        }
59    }
60
61    /// The fingerprint of the inviter.
62    pub fn fingerprint(&self) -> &Fingerprint {
63        match self {
64            Self::Contact { fingerprint, .. }
65            | Self::Group { fingerprint, .. }
66            | Self::Broadcast { fingerprint, .. } => fingerprint,
67        }
68    }
69
70    /// The `INVITENUMBER` of the setup-contact/secure-join protocol.
71    pub fn invitenumber(&self) -> &str {
72        match self {
73            Self::Contact { invitenumber, .. }
74            | Self::Group { invitenumber, .. }
75            | Self::Broadcast { invitenumber, .. } => invitenumber,
76        }
77    }
78
79    /// The `AUTH` code of the setup-contact/secure-join protocol.
80    pub fn authcode(&self) -> &str {
81        match self {
82            Self::Contact { authcode, .. }
83            | Self::Group { authcode, .. }
84            | Self::Broadcast { authcode, .. } => authcode,
85        }
86    }
87
88    pub fn is_v3(&self) -> bool {
89        match *self {
90            QrInvite::Contact { is_v3, .. } => is_v3,
91            QrInvite::Group { is_v3, .. } => is_v3,
92            QrInvite::Broadcast { is_v3, .. } => is_v3,
93        }
94    }
95}
96
97impl TryFrom<Qr> for QrInvite {
98    type Error = Error;
99
100    fn try_from(qr: Qr) -> Result<Self> {
101        match qr {
102            Qr::AskVerifyContact {
103                contact_id,
104                fingerprint,
105                invitenumber,
106                authcode,
107                is_v3,
108            } => Ok(QrInvite::Contact {
109                contact_id,
110                fingerprint,
111                invitenumber,
112                authcode,
113                is_v3,
114            }),
115            Qr::AskVerifyGroup {
116                grpname,
117                grpid,
118                contact_id,
119                fingerprint,
120                invitenumber,
121                authcode,
122                is_v3,
123            } => Ok(QrInvite::Group {
124                contact_id,
125                fingerprint,
126                name: grpname,
127                grpid,
128                invitenumber,
129                authcode,
130                is_v3,
131            }),
132            Qr::AskJoinBroadcast {
133                name,
134                grpid,
135                contact_id,
136                fingerprint,
137                authcode,
138                invitenumber,
139                is_v3,
140            } => Ok(QrInvite::Broadcast {
141                name,
142                grpid,
143                contact_id,
144                fingerprint,
145                authcode,
146                invitenumber,
147                is_v3,
148            }),
149            _ => bail!("Unsupported QR type"),
150        }
151    }
152}
153
154impl rusqlite::types::ToSql for QrInvite {
155    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
156        let json = serde_json::to_string(self)
157            .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
158        let val = rusqlite::types::Value::Text(json);
159        let out = rusqlite::types::ToSqlOutput::Owned(val);
160        Ok(out)
161    }
162}
163
164impl rusqlite::types::FromSql for QrInvite {
165    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
166        String::column_result(value).and_then(|val| {
167            serde_json::from_str(&val)
168                .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))
169        })
170    }
171}